Parcourir la source

Localize task scheduler settings panel (aecron.html)

Adds translations for the system settings page including: page header,
permission status banner (checking/enabled/disabled states), Open button,
task list table with headers, group cron permissions table, admin note,
and all dynamic JS strings (empty states, error messages, admin group note,
group permission toggle error).

Also adds locale-aware time unit strings (js/time/*) used by both pages,
replacing the hardcoded English day/hour/minute/sec plurals in
parseSecondsToHuman() with t() calls that produce compact CJK output
(e.g. "1天2小時30分") and natural English output ("1 day 2 hours").

Both pages share a single scheduler.json locale file.

https://claude.ai/code/session_01RH5mdJBjDjUZGKaVkHbKq6
Claude il y a 2 semaines
Parent
commit
f7bcaade25
2 fichiers modifiés avec 271 ajouts et 79 suppressions
  1. 90 72
      src/web/SystemAO/arsm/aecron.html
  2. 181 7
      src/web/SystemAO/locale/scheduler.json

+ 90 - 72
src/web/SystemAO/arsm/aecron.html

@@ -7,6 +7,7 @@
     <title>Tasks Scheduler</title>
     <script src="../../script/jquery.min.js"></script>
     <script src="../../script/ao_module.js"></script>
+    <script src="../../script/applocale.js"></script>
     <script src="../arsm/js/moment.min.js"></script>
     <style>
         * { box-sizing: border-box; margin: 0; padding: 0; }
@@ -200,8 +201,8 @@
     <div id="ae-page-header">
         <img src="img/scheduler.png" onerror="this.style.display='none';">
         <div>
-            <div class="ae-title">Tasks Scheduler</div>
-            <div class="ae-subtitle">Schedule tasks to run while you are offline</div>
+            <div class="ae-title" locale="aecron/page/title">Tasks Scheduler</div>
+            <div class="ae-subtitle" locale="aecron/page/subtitle">Schedule tasks to run while you are offline</div>
         </div>
     </div>
 
@@ -209,56 +210,65 @@
     <div id="ae-status-banner">
         <div id="ae-status-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="1.6"/><path d="M12 7v5l3 2" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
         <div id="ae-status-text">
-            <div class="ae-status-title">Checking permission…</div>
-            <div class="ae-status-desc">Please wait</div>
+            <div class="ae-status-title" locale="aecron/js/checking/title">Checking permission…</div>
+            <div class="ae-status-desc" locale="aecron/js/checking/desc">Please wait</div>
         </div>
     </div>
 
     <button id="ae-open-btn" style="display:none;" onclick="openSystemScheduler()">
-        <svg width="13" height="13" viewBox="0 0 16 16" fill="none" style="vertical-align:-1px;margin-right:5px"><circle cx="8" cy="8" r="2.5" stroke="currentColor" stroke-width="1.4"/><path d="M8 1.5v1M8 13.5v1M1.5 8h1M13.5 8h1M3.6 3.6l.7.7M11.7 11.7l.7.7M3.6 12.4l.7-.7M11.7 4.3l.7-.7" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>Open System Scheduler
+        <svg width="13" height="13" viewBox="0 0 16 16" fill="none" style="vertical-align:-1px;margin-right:5px"><circle cx="8" cy="8" r="2.5" stroke="currentColor" stroke-width="1.4"/><path d="M8 1.5v1M8 13.5v1M1.5 8h1M13.5 8h1M3.6 3.6l.7.7M11.7 11.7l.7.7M3.6 12.4l.7-.7M11.7 4.3l.7-.7" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
+        <span locale="aecron/btn/open">Open System Scheduler</span>
     </button>
 
     <!-- User's scheduled tasks -->
-    <div class="ae-section-label">Your Scheduled Tasks</div>
+    <div class="ae-section-label" locale="aecron/section/mytasks">Your Scheduled Tasks</div>
     <div class="ae-card">
         <table class="ae-table">
             <thead>
                 <tr>
-                    <th>Task / App</th>
-                    <th>Script</th>
-                    <th>Interval</th>
-                    <th>Base Time</th>
+                    <th locale="aecron/header/task-app">Task / App</th>
+                    <th locale="aecron/header/script">Script</th>
+                    <th locale="header/interval">Interval</th>
+                    <th locale="header/basetime">Base Time</th>
                 </tr>
             </thead>
             <tbody id="ae-task-list">
-                <tr class="ae-empty"><td colspan="4">Loading…</td></tr>
+                <tr class="ae-empty"><td colspan="4" locale="js/loading">Loading…</td></tr>
             </tbody>
         </table>
     </div>
 
     <!-- Admin: Group cron permissions (only shown to admins) -->
     <div id="ae-admin-section" style="display:none;">
-        <div class="ae-section-label">Group Cron Job Permissions</div>
+        <div class="ae-section-label" locale="aecron/section/groupperm">Group Cron Job Permissions</div>
         <div class="ae-card">
             <table class="ae-table">
                 <thead>
                     <tr>
-                        <th>Permission Group</th>
-                        <th style="width:160px;">Can Create Cron Jobs</th>
+                        <th locale="aecron/header/permgroup">Permission Group</th>
+                        <th style="width:160px;" locale="aecron/header/cancreate">Can Create Cron Jobs</th>
                     </tr>
                 </thead>
                 <tbody id="ae-group-perm-list">
-                    <tr class="ae-empty"><td colspan="2">Loading…</td></tr>
+                    <tr class="ae-empty"><td colspan="2" locale="js/loading">Loading…</td></tr>
                 </tbody>
             </table>
         </div>
-        <p style="font-size:12px;color:var(--ae-muted);margin-top:4px;">
+        <p style="font-size:12px;color:var(--ae-muted);margin-top:4px;" locale="aecron/admin/note">
             Administrator groups always have cron creation permission and cannot be restricted.
         </p>
     </div>
 </div>
 
 <script>
+    /* ── i18n helper ── */
+    function t(key, fallback) {
+        if (typeof applocale !== 'undefined') {
+            return applocale.getString(key, fallback);
+        }
+        return fallback;
+    }
+
     /* ── Theme detection ── */
     (function() {
         var root = document.getElementById('ae-root');
@@ -273,12 +283,12 @@
             }
         } catch(e) {}
         try {
-            var t = null;
-            if (typeof preferredTheme !== 'undefined') t = preferredTheme;
-            else if (parent && typeof parent.preferredTheme !== 'undefined') t = parent.preferredTheme;
-            if (!t) { try { t = localStorage.getItem('preferredTheme'); } catch(e) {} }
-            if (t) {
-                var isDark = t === 'dark' || t === 'darkTheme';
+            var theme = null;
+            if (typeof preferredTheme !== 'undefined') theme = preferredTheme;
+            else if (parent && typeof parent.preferredTheme !== 'undefined') theme = parent.preferredTheme;
+            if (!theme) { try { theme = localStorage.getItem('preferredTheme'); } catch(e) {} }
+            if (theme) {
+                var isDark = theme === 'dark' || theme === 'darkTheme';
                 root.classList.toggle('dark', isDark);
                 document.body.classList.toggle('dark', isDark);
             }
@@ -290,50 +300,55 @@
         var d = Math.floor(s / 86400);
         var h = Math.floor((s % 86400) / 3600);
         var m = Math.floor((s % 3600) / 60);
+        var sep = t('js/time/sep', ' ');
         var parts = [];
-        if (d) parts.push(d + (d === 1 ? " day" : " days"));
-        if (h) parts.push(h + (h === 1 ? " hour" : " hours"));
-        if (m) parts.push(m + (m === 1 ? " minute" : " minutes"));
-        return parts.length ? parts.join(" ") : s + " sec";
+        if (d) parts.push(d + t(d === 1 ? 'js/time/day1'    : 'js/time/days',    d === 1 ? ' day'    : ' days'));
+        if (h) parts.push(h + t(h === 1 ? 'js/time/hour1'   : 'js/time/hours',   h === 1 ? ' hour'   : ' hours'));
+        if (m) parts.push(m + t(m === 1 ? 'js/time/minute1' : 'js/time/minutes', m === 1 ? ' minute' : ' minutes'));
+        return parts.length ? parts.join(sep) : s + t('js/time/sec', ' sec');
     }
 
     /* ── Check user cron permission ── */
-    $.ajax({
-        url: "../../system/arsm/aecron/permission",
-        success: function(data) {
-            var banner = document.getElementById('ae-status-banner');
-            var icon   = document.getElementById('ae-status-icon');
-            var title  = banner.querySelector('.ae-status-title');
-            var desc   = banner.querySelector('.ae-status-desc');
-            if (data.CanCreate) {
-                banner.classList.add('permitted');
-                icon.innerHTML = '<svg width="22" height="22" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="1.6"/><path d="M7 12.5l3.5 3.5 6.5-7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
-                title.textContent = 'Cron Job Creation Enabled';
-                desc.textContent  = 'You are allowed to create and manage scheduled tasks.';
-            } else {
-                banner.classList.add('denied');
-                icon.innerHTML = '<svg width="22" height="22" viewBox="0 0 24 24" fill="none"><rect x="6" y="11" width="12" height="10" rx="1.5" stroke="currentColor" stroke-width="1.6"/><path d="M8 11V7.5a4 4 0 018 0V11" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>';
-                title.textContent = 'Cron Job Creation Disabled';
-                desc.textContent  = 'Your account does not have permission to create scheduled tasks. Contact an administrator.';
+    function checkPermission() {
+        $.ajax({
+            url: "../../system/arsm/aecron/permission",
+            success: function(data) {
+                var banner = document.getElementById('ae-status-banner');
+                var icon   = document.getElementById('ae-status-icon');
+                var title  = banner.querySelector('.ae-status-title');
+                var desc   = banner.querySelector('.ae-status-desc');
+                if (data.CanCreate) {
+                    banner.classList.add('permitted');
+                    icon.innerHTML = '<svg width="22" height="22" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="1.6"/><path d="M7 12.5l3.5 3.5 6.5-7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
+                    title.textContent = t('aecron/js/status/enabled/title', 'Cron Job Creation Enabled');
+                    desc.textContent  = t('aecron/js/status/enabled/desc',  'You are allowed to create and manage scheduled tasks.');
+                } else {
+                    banner.classList.add('denied');
+                    icon.innerHTML = '<svg width="22" height="22" viewBox="0 0 24 24" fill="none"><rect x="6" y="11" width="12" height="10" rx="1.5" stroke="currentColor" stroke-width="1.6"/><path d="M8 11V7.5a4 4 0 018 0V11" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>';
+                    title.textContent = t('aecron/js/status/disabled/title', 'Cron Job Creation Disabled');
+                    desc.textContent  = t('aecron/js/status/disabled/desc',  'Your account does not have permission to create scheduled tasks. Contact an administrator.');
+                }
             }
-        }
-    });
+        });
+    }
 
     /* ── Check if user has access to Task Scheduler module (show Open button) ── */
-    $.ajax({
-        url: "../../system/modules/list",
-        success: function(data) {
-            var hasAccess = false;
-            if (Array.isArray(data)) {
-                data.forEach(function(m) {
-                    if (m.Name === "Tasks Scheduler") hasAccess = true;
-                });
-            }
-            if (hasAccess) {
-                document.getElementById('ae-open-btn').style.display = 'inline-flex';
+    function checkModuleAccess() {
+        $.ajax({
+            url: "../../system/modules/list",
+            success: function(data) {
+                var hasAccess = false;
+                if (Array.isArray(data)) {
+                    data.forEach(function(m) {
+                        if (m.Name === "Tasks Scheduler") hasAccess = true;
+                    });
+                }
+                if (hasAccess) {
+                    document.getElementById('ae-open-btn').style.display = 'inline-flex';
+                }
             }
-        }
-    });
+        });
+    }
 
     /* ── Load user's task list ── */
     function loadTaskList() {
@@ -343,25 +358,24 @@
                 var tbody = document.getElementById('ae-task-list');
                 tbody.innerHTML = "";
                 if (!data || data.length === 0) {
-                    tbody.innerHTML = '<tr class="ae-empty"><td colspan="4">No scheduled tasks registered.</td></tr>';
+                    tbody.innerHTML = '<tr class="ae-empty"><td colspan="4">' + t('aecron/js/empty/notask', 'No scheduled tasks registered.') + '</td></tr>';
                     return;
                 }
                 data.forEach(function(task) {
                     var scriptName = (task.ScriptVpath || "").split("/").pop();
                     var appBadge   = task.AppName ? '<span class="ae-app-badge">' + escapeHtml(task.AppName) + '</span>' : '';
-                    var nameCell   = escapeHtml(task.Name) + appBadge;
                     var tr = document.createElement('tr');
                     tr.innerHTML =
                         '<td><span title="' + escapeHtml(task.ScriptVpath || "") + '">' + escapeHtml(scriptName) + '</span>' + appBadge + '</td>' +
                         '<td style="color:var(--ae-muted);font-size:12px;">' + escapeHtml(task.ScriptVpath || "") + '</td>' +
-                        '<td>Every ' + parseSecondsToHuman(task.ExecutionInterval) + '</td>' +
+                        '<td>' + t('js/task/every', 'Every ') + parseSecondsToHuman(task.ExecutionInterval) + '</td>' +
                         '<td style="font-size:12px;">' + moment.unix(task.BaseTime).format('lll') + '</td>';
                     tbody.appendChild(tr);
                 });
             },
             error: function() {
                 document.getElementById('ae-task-list').innerHTML =
-                    '<tr class="ae-empty"><td colspan="4">Unable to load task list.</td></tr>';
+                    '<tr class="ae-empty"><td colspan="4">' + t('aecron/js/error/loadfail', 'Unable to load task list.') + '</td></tr>';
             }
         });
     }
@@ -375,7 +389,7 @@
                 var tbody = document.getElementById('ae-group-perm-list');
                 tbody.innerHTML = "";
                 if (!data || data.length === 0) {
-                    tbody.innerHTML = '<tr class="ae-empty"><td colspan="2">No permission groups found.</td></tr>';
+                    tbody.innerHTML = '<tr class="ae-empty"><td colspan="2">' + t('aecron/js/empty/nogroups', 'No permission groups found.') + '</td></tr>';
                     return;
                 }
                 data.forEach(function(entry) {
@@ -383,7 +397,7 @@
                     var checked = entry.CanCreateCronJob ? ' checked' : '';
                     var lockClass = isAdmin ? ' admin-locked' : '';
                     var adminNote = isAdmin
-                        ? '<div class="ae-group-admin-note">Administrator group — always enabled</div>'
+                        ? '<div class="ae-group-admin-note">' + t('aecron/js/admingroup/note', 'Administrator group — always enabled') + '</div>'
                         : '';
                     var tr = document.createElement('tr');
                     tr.innerHTML =
@@ -402,8 +416,8 @@
                     tbody.appendChild(tr);
                 });
             },
-            error: function(xhr) {
-                // Not admin or endpoint not available  silently skip
+            error: function() {
+                // Not admin or endpoint not available  silently skip
             }
         });
     }
@@ -416,9 +430,8 @@
             method: "POST",
             data: { group: groupName, allow: allow ? "true" : "false" },
             error: function() {
-                // Revert on failure
                 checkbox.checked = !allow;
-                alert("Failed to update group permission.");
+                alert(t('aecron/js/error/groupperm', 'Failed to update group permission.'));
             }
         });
     }
@@ -427,7 +440,7 @@
         ao_module_newfw({
             url: "SystemAO/arsm/scheduler.html",
             appicon: "SystemAO/arsm/img/scheduler.png",
-            title: "Task Scheduler"
+            title: t('aecron/page/title', 'Task Scheduler')
         });
     }
 
@@ -439,9 +452,14 @@
             .replace(/"/g, "&quot;");
     }
 
-    // Init
-    loadTaskList();
-    loadGroupPermissions();
+    /* ── Init ── */
+    applocale.init("../locale/scheduler.json", function() {
+        applocale.translate();
+        checkPermission();
+        checkModuleAccess();
+        loadTaskList();
+        loadGroupPermissions();
+    });
 </script>
 </body>
 </html>

+ 181 - 7
src/web/SystemAO/locale/scheduler.json

@@ -66,7 +66,36 @@
                 "js/alert/noname": "任務名稱為必填。",
                 "js/alert/nopath": "腳本路徑為必填。",
                 "js/alert/interval": "執行間隔至少需要 1 分鐘。",
-                "js/toast/created": "任務已建立!"
+                "js/toast/created": "任務已建立!",
+                "js/time/day1": "天",
+                "js/time/days": "天",
+                "js/time/hour1": "小時",
+                "js/time/hours": "小時",
+                "js/time/minute1": "分鐘",
+                "js/time/minutes": "分鐘",
+                "js/time/sec": "秒",
+                "js/time/sep": "",
+                "aecron/page/title": "任務排程器",
+                "aecron/page/subtitle": "在您離線時執行排程任務",
+                "aecron/btn/open": "開啟系統排程器",
+                "aecron/section/mytasks": "我的排程任務",
+                "aecron/header/task-app": "任務 / 應用程式",
+                "aecron/header/script": "腳本",
+                "aecron/section/groupperm": "群組 Cron 任務權限",
+                "aecron/header/permgroup": "權限群組",
+                "aecron/header/cancreate": "可建立 Cron 任務",
+                "aecron/admin/note": "管理員群組始終擁有 cron 任務建立權限,且無法被限制。",
+                "aecron/js/checking/title": "正在檢查權限…",
+                "aecron/js/checking/desc": "請稍候",
+                "aecron/js/status/enabled/title": "Cron 任務建立已啟用",
+                "aecron/js/status/enabled/desc": "您可以建立和管理排程任務。",
+                "aecron/js/status/disabled/title": "Cron 任務建立已停用",
+                "aecron/js/status/disabled/desc": "您的帳戶沒有建立排程任務的權限。請聯絡管理員。",
+                "aecron/js/empty/notask": "沒有已登記的排程任務。",
+                "aecron/js/error/loadfail": "無法載入任務清單。",
+                "aecron/js/empty/nogroups": "找不到權限群組。",
+                "aecron/js/admingroup/note": "管理員群組 — 始終啟用",
+                "aecron/js/error/groupperm": "更新群組權限失敗。"
             },
             "titles": {},
             "placeholder": {
@@ -140,7 +169,36 @@
                 "js/alert/noname": "任務名稱為必填。",
                 "js/alert/nopath": "腳本路徑為必填。",
                 "js/alert/interval": "執行間隔至少需要 1 分鐘。",
-                "js/toast/created": "任務已建立!"
+                "js/toast/created": "任務已建立!",
+                "js/time/day1": "天",
+                "js/time/days": "天",
+                "js/time/hour1": "小時",
+                "js/time/hours": "小時",
+                "js/time/minute1": "分鐘",
+                "js/time/minutes": "分鐘",
+                "js/time/sec": "秒",
+                "js/time/sep": "",
+                "aecron/page/title": "任務排程器",
+                "aecron/page/subtitle": "在您離線時執行排程任務",
+                "aecron/btn/open": "開啟系統排程器",
+                "aecron/section/mytasks": "我的排程任務",
+                "aecron/header/task-app": "任務 / 應用程式",
+                "aecron/header/script": "腳本",
+                "aecron/section/groupperm": "群組 Cron 任務權限",
+                "aecron/header/permgroup": "權限群組",
+                "aecron/header/cancreate": "可建立 Cron 任務",
+                "aecron/admin/note": "管理員群組始終擁有 cron 任務建立權限,且無法被限制。",
+                "aecron/js/checking/title": "正在檢查權限…",
+                "aecron/js/checking/desc": "請稍候",
+                "aecron/js/status/enabled/title": "Cron 任務建立已啟用",
+                "aecron/js/status/enabled/desc": "您可以建立和管理排程任務。",
+                "aecron/js/status/disabled/title": "Cron 任務建立已停用",
+                "aecron/js/status/disabled/desc": "您的帳戶沒有建立排程任務的權限。請聯絡管理員。",
+                "aecron/js/empty/notask": "沒有已登記的排程任務。",
+                "aecron/js/error/loadfail": "無法載入任務清單。",
+                "aecron/js/empty/nogroups": "找不到權限群組。",
+                "aecron/js/admingroup/note": "管理員群組 — 始終啟用",
+                "aecron/js/error/groupperm": "更新群組權限失敗。"
             },
             "titles": {},
             "placeholder": {
@@ -214,7 +272,36 @@
                 "js/alert/noname": "任务名称为必填项。",
                 "js/alert/nopath": "脚本路径为必填项。",
                 "js/alert/interval": "执行间隔至少需要 1 分钟。",
-                "js/toast/created": "任务已创建!"
+                "js/toast/created": "任务已创建!",
+                "js/time/day1": "天",
+                "js/time/days": "天",
+                "js/time/hour1": "小时",
+                "js/time/hours": "小时",
+                "js/time/minute1": "分钟",
+                "js/time/minutes": "分钟",
+                "js/time/sec": "秒",
+                "js/time/sep": "",
+                "aecron/page/title": "任务计划程序",
+                "aecron/page/subtitle": "在您离线时执行计划任务",
+                "aecron/btn/open": "打开系统计划程序",
+                "aecron/section/mytasks": "我的计划任务",
+                "aecron/header/task-app": "任务 / 应用",
+                "aecron/header/script": "脚本",
+                "aecron/section/groupperm": "群组 Cron 任务权限",
+                "aecron/header/permgroup": "权限群组",
+                "aecron/header/cancreate": "可创建 Cron 任务",
+                "aecron/admin/note": "管理员组始终拥有 cron 任务创建权限,且无法被限制。",
+                "aecron/js/checking/title": "正在检查权限…",
+                "aecron/js/checking/desc": "请稍候",
+                "aecron/js/status/enabled/title": "Cron 任务创建已启用",
+                "aecron/js/status/enabled/desc": "您可以创建和管理计划任务。",
+                "aecron/js/status/disabled/title": "Cron 任务创建已停用",
+                "aecron/js/status/disabled/desc": "您的账户没有创建计划任务的权限。请联系管理员。",
+                "aecron/js/empty/notask": "没有已注册的计划任务。",
+                "aecron/js/error/loadfail": "无法加载任务列表。",
+                "aecron/js/empty/nogroups": "未找到权限组。",
+                "aecron/js/admingroup/note": "管理员组 — 始终启用",
+                "aecron/js/error/groupperm": "更新组权限失败。"
             },
             "titles": {},
             "placeholder": {
@@ -258,7 +345,7 @@
                 "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/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",
@@ -288,7 +375,36 @@
                 "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!"
+                "js/toast/created": "Task created!",
+                "js/time/day1": " day",
+                "js/time/days": " days",
+                "js/time/hour1": " hour",
+                "js/time/hours": " hours",
+                "js/time/minute1": " minute",
+                "js/time/minutes": " minutes",
+                "js/time/sec": " sec",
+                "js/time/sep": " ",
+                "aecron/page/title": "Tasks Scheduler",
+                "aecron/page/subtitle": "Schedule tasks to run while you are offline",
+                "aecron/btn/open": "Open System Scheduler",
+                "aecron/section/mytasks": "Your Scheduled Tasks",
+                "aecron/header/task-app": "Task / App",
+                "aecron/header/script": "Script",
+                "aecron/section/groupperm": "Group Cron Job Permissions",
+                "aecron/header/permgroup": "Permission Group",
+                "aecron/header/cancreate": "Can Create Cron Jobs",
+                "aecron/admin/note": "Administrator groups always have cron creation permission and cannot be restricted.",
+                "aecron/js/checking/title": "Checking permission…",
+                "aecron/js/checking/desc": "Please wait",
+                "aecron/js/status/enabled/title": "Cron Job Creation Enabled",
+                "aecron/js/status/enabled/desc": "You are allowed to create and manage scheduled tasks.",
+                "aecron/js/status/disabled/title": "Cron Job Creation Disabled",
+                "aecron/js/status/disabled/desc": "Your account does not have permission to create scheduled tasks. Contact an administrator.",
+                "aecron/js/empty/notask": "No scheduled tasks registered.",
+                "aecron/js/error/loadfail": "Unable to load task list.",
+                "aecron/js/empty/nogroups": "No permission groups found.",
+                "aecron/js/admingroup/note": "Administrator group — always enabled",
+                "aecron/js/error/groupperm": "Failed to update group permission."
             },
             "titles": {},
             "placeholder": {
@@ -362,7 +478,36 @@
                 "js/alert/noname": "タスク名は必須です。",
                 "js/alert/nopath": "スクリプトパスは必須です。",
                 "js/alert/interval": "実行間隔は1分以上にする必要があります。",
-                "js/toast/created": "タスクを作成しました!"
+                "js/toast/created": "タスクを作成しました!",
+                "js/time/day1": "日",
+                "js/time/days": "日",
+                "js/time/hour1": "時間",
+                "js/time/hours": "時間",
+                "js/time/minute1": "分",
+                "js/time/minutes": "分",
+                "js/time/sec": "秒",
+                "js/time/sep": "",
+                "aecron/page/title": "タスクスケジューラ",
+                "aecron/page/subtitle": "オフライン中にタスクを実行するよう設定します",
+                "aecron/btn/open": "システムスケジューラを開く",
+                "aecron/section/mytasks": "マイスケジュールタスク",
+                "aecron/header/task-app": "タスク / アプリ",
+                "aecron/header/script": "スクリプト",
+                "aecron/section/groupperm": "グループ Cron 権限",
+                "aecron/header/permgroup": "権限グループ",
+                "aecron/header/cancreate": "Cron タスクの作成可否",
+                "aecron/admin/note": "管理者グループは常に Cron タスク作成権限を持ち、制限することはできません。",
+                "aecron/js/checking/title": "権限を確認中…",
+                "aecron/js/checking/desc": "しばらくお待ちください",
+                "aecron/js/status/enabled/title": "Cron タスク作成が有効",
+                "aecron/js/status/enabled/desc": "スケジュールタスクを作成・管理できます。",
+                "aecron/js/status/disabled/title": "Cron タスク作成が無効",
+                "aecron/js/status/disabled/desc": "アカウントにスケジュールタスクを作成する権限がありません。管理者に連絡してください。",
+                "aecron/js/empty/notask": "スケジュールタスクは登録されていません。",
+                "aecron/js/error/loadfail": "タスクリストを読み込めません。",
+                "aecron/js/empty/nogroups": "権限グループが見つかりません。",
+                "aecron/js/admingroup/note": "管理者グループ — 常に有効",
+                "aecron/js/error/groupperm": "グループ権限の更新に失敗しました。"
             },
             "titles": {},
             "placeholder": {
@@ -436,7 +581,36 @@
                 "js/alert/noname": "작업 이름은 필수입니다.",
                 "js/alert/nopath": "스크립트 경로는 필수입니다.",
                 "js/alert/interval": "실행 간격은 최소 1분이어야 합니다.",
-                "js/toast/created": "작업이 생성되었습니다!"
+                "js/toast/created": "작업이 생성되었습니다!",
+                "js/time/day1": "일",
+                "js/time/days": "일",
+                "js/time/hour1": "시간",
+                "js/time/hours": "시간",
+                "js/time/minute1": "분",
+                "js/time/minutes": "분",
+                "js/time/sec": "초",
+                "js/time/sep": "",
+                "aecron/page/title": "작업 스케줄러",
+                "aecron/page/subtitle": "오프라인 상태에서 작업을 실행하도록 예약합니다",
+                "aecron/btn/open": "시스템 스케줄러 열기",
+                "aecron/section/mytasks": "내 예약 작업",
+                "aecron/header/task-app": "작업 / 앱",
+                "aecron/header/script": "스크립트",
+                "aecron/section/groupperm": "그룹 Cron 권한",
+                "aecron/header/permgroup": "권한 그룹",
+                "aecron/header/cancreate": "Cron 작업 생성 가능",
+                "aecron/admin/note": "관리자 그룹은 항상 Cron 작업 생성 권한을 가지며 제한할 수 없습니다.",
+                "aecron/js/checking/title": "권한 확인 중…",
+                "aecron/js/checking/desc": "잠시 기다려 주세요",
+                "aecron/js/status/enabled/title": "Cron 작업 생성 활성화",
+                "aecron/js/status/enabled/desc": "예약 작업을 생성하고 관리할 수 있습니다.",
+                "aecron/js/status/disabled/title": "Cron 작업 생성 비활성화",
+                "aecron/js/status/disabled/desc": "계정에 예약 작업을 생성할 권한이 없습니다. 관리자에게 문의하세요.",
+                "aecron/js/empty/notask": "등록된 예약 작업이 없습니다.",
+                "aecron/js/error/loadfail": "작업 목록을 불러올 수 없습니다.",
+                "aecron/js/empty/nogroups": "권한 그룹을 찾을 수 없습니다.",
+                "aecron/js/admingroup/note": "관리자 그룹 — 항상 활성화",
+                "aecron/js/error/groupperm": "그룹 권한 업데이트에 실패했습니다."
             },
             "titles": {},
             "placeholder": {