Jelajahi Sumber

Revamp cluster UI and metadata persistence

Split cluster administration into dedicated Settings, Info, and Jobs pages with shared styling/helpers, full cluster localization, and a new `-disable_cluster` flag. The cluster drive is now only mounted when the node is in a cluster with at least one volume, and storage settings add a cluster-wide auto read-only toggle for nearly full volumes.

Rework cluster metadata persistence to use batched write-behind disk flushes via new database batch writes, preventing store lock contention and lease starvation while adding coverage for the new persistence paths. This also tightens pending-log draining, syncs snapshot change hooks, and updates docs/examples to match the new cluster UX. Miscellaneous fixes improve special URI decoding, media path handling, and failed-upload retry UI.
Toby Chui 2 minggu lalu
induk
melakukan
3622f25c8d
34 mengubah file dengan 5113 tambahan dan 760 penghapusan
  1. 14 4
      CLAUDE.md
  2. 2 0
      README.md
  3. 2 2
      examples/clusters_jobs/README.md
  4. 22 4
      examples/clusters_jobs/ffmpeg_convert/ffmpeg_convert.agi
  5. 1 1
      examples/clusters_jobs/hello_world/hello_world.agi
  6. 24 6
      examples/clusters_jobs/list_files/list_files.agi
  7. 72 15
      src/cluster.go
  8. 3 3
      src/file_system.go
  9. 2 1
      src/flags.go
  10. 2 1
      src/main.router.go
  11. 60 10
      src/mod/cluster/README.md
  12. 31 2
      src/mod/cluster/metadata/log.go
  13. 15 1
      src/mod/cluster/metadata/manager.go
  14. 184 0
      src/mod/cluster/metadata/persist.go
  15. 158 0
      src/mod/cluster/metadata/persist_test.go
  16. 44 28
      src/mod/cluster/metadata/store.go
  17. 24 5
      src/mod/cluster/storage/handlers_admin.go
  18. 1 1
      src/mod/cluster/storage/service.go
  19. 52 0
      src/mod/cluster/storage/storage_test.go
  20. 61 5
      src/mod/cluster/storage/volumes.go
  21. 28 0
      src/mod/database/database.go
  22. 87 0
      src/mod/database/database_batch_test.go
  23. 36 0
      src/mod/database/database_core.go
  24. 34 0
      src/mod/database/database_openwrt.go
  25. 3 4
      src/mod/media/mediaserver/mediaserver.go
  26. 226 0
      src/web/SystemAO/cluster/cluster.common.js
  27. 149 0
      src/web/SystemAO/cluster/cluster.css
  28. 197 594
      src/web/SystemAO/cluster/cluster.html
  29. 424 0
      src/web/SystemAO/cluster/clusterinfo.html
  30. 53 72
      src/web/SystemAO/cluster/jobs.html
  31. 25 1
      src/web/SystemAO/file_system/file_explorer.css
  32. 8 0
      src/web/SystemAO/file_system/js/explorer/uploadui.js
  33. 3048 0
      src/web/SystemAO/locale/cluster.json
  34. 21 0
      src/web/SystemAO/locale/system_settings.json

+ 14 - 4
CLAUDE.md

@@ -269,9 +269,14 @@ before changing how a phase works, but start new work from the packages below.
   is the cluster agent: create / join (pasteable join tokens) / leave, records
   merged last-writer-wins by gossip, heartbeats, computed node states, health
   and capability manifests ([`src/mod/cluster/capability/`](src/mod/cluster/capability/)).
-  Admin API `/system/cluster/*` and the System Settings page
-  [`src/web/SystemAO/cluster/cluster.html`](src/web/SystemAO/cluster/cluster.html);
-  wiring in [`src/cluster.go`](src/cluster.go).
+  Admin API `/system/cluster/*` and the System Settings tabs Cluster
+  Settings ([`cluster.html`](src/web/SystemAO/cluster/cluster.html)), Cluster
+  Info ([`clusterinfo.html`](src/web/SystemAO/cluster/clusterinfo.html)) and
+  Cluster Jobs; all three are localized through
+  [`src/web/SystemAO/locale/cluster.json`](src/web/SystemAO/locale/cluster.json)
+  (server messages included, via `CL.tr`), so add new user-facing strings and
+  server messages there. Wiring in [`src/cluster.go`](src/cluster.go);
+  `-disable_cluster` turns the whole feature off.
 - **Identity** ([`src/mod/cluster/identity/`](src/mod/cluster/identity/)):
   one member is the *identity origin*; other members forward logins to it
   through the auth agent's `ForwardAuth` hook (password hash only), mirror the
@@ -286,10 +291,15 @@ before changing how a phase works, but start new work from the packages below.
   replicated log with catch-up and snapshots. Write through
   `Submit(kind, record)`, read through `Stat` / `ListDir` / `Volumes` /
   `PolicyFor`; new cluster tables go through `membership.RegisterClusterTable`.
+  The disk copy is written behind (`persist.go`, batched through
+  `database.WriteBatch`): never add a synced disk write inside the store
+  lock, or a write burst starves lease renewal and the leader loses its lease.
 - **Storage and the `cluster:/` drive**
   ([`src/mod/cluster/storage/`](src/mod/cluster/storage/),
   [`src/mod/filesystem/abstractions/clusterfs/`](src/mod/filesystem/abstractions/clusterfs/)):
   admins contribute folders (volumes); files stay whole files inside them.
+  The drive is only mounted while the node is in a cluster that has at least
+  one volume (`clusterSyncDrive`), so otherwise nothing sees or scans it.
   Writes spool and hash locally, get placed by the leader, are copied
   (locally or by the 4 MiB chunked, SHA-256 verified `store/*` protocol) and
   only then published. The core mounts the drive into the base storage pool
@@ -310,7 +320,7 @@ before changing how a phase works, but start new work from the packages below.
   one weighted scorer answers "which node?" for jobs, write placement and
   replication. Weights (locality, free CPU/RAM/disk, queue depth, network
   distance, health, wanted features) are a replicated cluster setting edited
-  on the Cluster page; every score carries its factors, so
+  on the Cluster Settings page; every score carries its factors, so
   `/system/cluster/sched/explain?job=<id>` can show why a node won. Heartbeat
   round trips feed `NodeView.LatencyMs`, and a volume under 5 % free goes
   read only with a `node.diskfull` event. For a second copy the planner also

+ 2 - 0
README.md

@@ -195,6 +195,8 @@ The following startup parameters are supported (v2.016)
         Enable directory listing (default true)
   -disable_http
         Disable HTTP server, require tls=true
+  -disable_cluster
+        Disable the cluster features (joining, the cluster:/ drive, cluster jobs and their settings pages)
   -disable_ip_resolver
         Disable IP resolving if the system is running under reverse proxy environment
   -disable_subservice

+ 2 - 2
examples/clusters_jobs/README.md

@@ -13,7 +13,7 @@ pair of files:
 
 Keep both files of a pair in the same folder: the launcher refers to its job
 by a relative path. The node you run the launcher on must be a member of a
-cluster (System Settings > Cluster).
+cluster (System Settings > Cluster Settings).
 
 | Example | What it shows |
 |---|---|
@@ -61,7 +61,7 @@ var rec = cluster.jobs.wait(id, 60);   // throws if it is still running after 60
 `cluster.jobs.status(id)`, `cluster.jobs.list()` and `cluster.jobs.cancel(id)`
 work on jobs you submitted. Every job also appears on System Settings >
 Cluster Jobs, and an administrator can see why a job landed where it did on
-the Scheduling card of the Cluster page.
+the Scheduling card of the Cluster Info page.
 
 A job that no node can take yet stays queued, and `rec.state.reason` says
 why, for example that no online node has ffmpeg.

+ 22 - 4
examples/clusters_jobs/ffmpeg_convert/ffmpeg_convert.agi

@@ -6,7 +6,7 @@
     names its input file, and the scheduler picks a node that has ffmpeg,
     preferring one that already stores demo.mp4.
 
-    Optional GET/POST parameters:
+    Optional parameters, in the URL query or a form body:
         input   default cluster:/demo.mp4
         output  default cluster:/demo.webm
 
@@ -19,13 +19,31 @@
 
 requirelib("cluster");
 
+//Read a request parameter. A script run as a Serverless endpoint gets the
+//request through getPara() (URL query) and postPara() (form body); a WebApp
+//backend script gets its POST fields as global variables instead.
+function requestParam(name) {
+    var v = null;
+    if (typeof getPara === "function") {
+        v = getPara(name);
+    }
+    if ((v === null || v === "") && typeof postPara === "function") {
+        v = postPara(name);
+    }
+    if (v === null || v === "") {
+        var globalScope = (function () { return this; })();
+        v = (typeof globalScope[name] === "string") ? globalScope[name] : "";
+    }
+    return v === null ? "" : String(v);
+}
+
 function main() {
     if (!cluster.inCluster()) {
-        return { error: "This node is not part of a cluster. Create or join one in System Settings > Cluster." };
+        return { error: "This node is not part of a cluster. Create or join one in System Settings > Cluster Settings." };
     }
 
-    var src = (typeof input !== "undefined" && input !== "") ? input : "cluster:/demo.mp4";
-    var dst = (typeof output !== "undefined" && output !== "") ? output : "cluster:/demo.webm";
+    var src = requestParam("input") || "cluster:/demo.mp4";
+    var dst = requestParam("output") || "cluster:/demo.webm";
 
     var id = cluster.jobs.submit({
         name: "Convert " + src.split("/").pop() + " to " + dst.split(".").pop().toUpperCase(),

+ 1 - 1
examples/clusters_jobs/hello_world/hello_world.agi

@@ -23,7 +23,7 @@ var WAIT_SECONDS = 60;
 
 function main() {
     if (!cluster.inCluster()) {
-        return { error: "This node is not part of a cluster. Create or join one in System Settings > Cluster." };
+        return { error: "This node is not part of a cluster. Create or join one in System Settings > Cluster Settings." };
     }
 
     var submitted = [];

+ 24 - 6
examples/clusters_jobs/list_files/list_files.agi

@@ -5,7 +5,7 @@
     its output: every file under cluster:/ with its size and extension, and
     a size total per extension.
 
-    Optional GET/POST parameters:
+    Optional parameters, in the URL query or a form body:
         root   folder to list, default cluster:/
         limit  maximum files in the list, default 5000
 
@@ -14,17 +14,35 @@
 
 requirelib("cluster");
 
+//Read a request parameter. A script run as a Serverless endpoint gets the
+//request through getPara() (URL query) and postPara() (form body); a WebApp
+//backend script gets its POST fields as global variables instead.
+function requestParam(name) {
+    var v = null;
+    if (typeof getPara === "function") {
+        v = getPara(name);
+    }
+    if ((v === null || v === "") && typeof postPara === "function") {
+        v = postPara(name);
+    }
+    if (v === null || v === "") {
+        var globalScope = (function () { return this; })();
+        v = (typeof globalScope[name] === "string") ? globalScope[name] : "";
+    }
+    return v === null ? "" : String(v);
+}
+
 function main() {
     if (!cluster.inCluster()) {
-        return { error: "This node is not part of a cluster. Create or join one in System Settings > Cluster." };
+        return { error: "This node is not part of a cluster. Create or join one in System Settings > Cluster Settings." };
     }
 
     var args = {};
-    if (typeof root !== "undefined" && root !== "") {
-        args.root = root;
+    if (requestParam("root") !== "") {
+        args.root = requestParam("root");
     }
-    if (typeof limit !== "undefined" && parseInt(limit) > 0) {
-        args.limit = parseInt(limit);
+    if (parseInt(requestParam("limit")) > 0) {
+        args.limit = parseInt(requestParam("limit"));
     }
 
     var id = cluster.jobs.submit({

+ 72 - 15
src/cluster.go

@@ -280,6 +280,36 @@ func (b *clusterBackend) Ready() bool { return b.s.Ready() }
 
 // clusterMountDrive attaches the cluster:/ drive to the base storage pool so
 // every user sees it, and clusterUnmountDrive removes it again.
+// clusterDriveWanted reports whether cluster:/ should be visible: this node
+// is in a cluster and the cluster has at least one volume to keep files in.
+// Without either, the drive is left out of File Manager and every other part
+// of the system, nightly tasks included.
+func clusterDriveWanted() bool {
+	if clusterManager == nil || clusterStorage == nil || clusterMetadata == nil || !clusterManager.InCluster() {
+		return false
+	}
+	return clusterHasVolume(clusterMetadata.Volumes())
+}
+
+// clusterHasVolume reports whether any volume is still part of the cluster.
+func clusterHasVolume(vols []metadata.Volume) bool {
+	for _, v := range vols {
+		if !v.Removed {
+			return true
+		}
+	}
+	return false
+}
+
+// clusterSyncDrive mounts or unmounts cluster:/ to match clusterDriveWanted.
+func clusterSyncDrive() {
+	if clusterDriveWanted() {
+		clusterMountDrive()
+	} else {
+		clusterUnmountDrive()
+	}
+}
+
 func clusterMountDrive() {
 	clusterMountMu.Lock()
 	defer clusterMountMu.Unlock()
@@ -386,8 +416,19 @@ func (p *clusterHealthProvider) snapshot() membership.Health {
 }
 
 func ClusterInit() {
-	//Cluster agent: always available so a node can create or join a cluster
-	//from System Settings regardless of the LAN discovery features
+	if *disable_cluster {
+		systemWideLogger.PrintAndLog("Cluster", "Cluster features are disabled by the -disable_cluster flag", nil)
+	} else {
+		clusterStartAgent()
+	}
+	clusterStartNeighbourhood()
+}
+
+// clusterStartAgent starts the cluster agent and every cluster service on
+// top of it. It is always available unless -disable_cluster is set, so a node
+// can create or join a cluster from System Settings regardless of the LAN
+// discovery features.
+func clusterStartAgent() {
 	health := &clusterHealthProvider{}
 	manager, err := membership.NewManager(membership.Option{
 		NodeID:      deviceUUID,
@@ -401,6 +442,23 @@ func ClusterInit() {
 		systemWideLogger.PrintAndLog("Cluster", "Unable to start cluster agent: "+err.Error(), err)
 	} else {
 		clusterManager = manager
+		//Settings first, then Info; Cluster Jobs is added once the job runtime starts
+		registerSetting(settingModule{
+			Name:         "Cluster Settings",
+			Desc:         "Create or join a cluster and set up its storage and scheduling",
+			IconPath:     "SystemAO/cluster/img/small_icon.png",
+			Group:        "Cluster",
+			StartDir:     "SystemAO/cluster/cluster.html",
+			RequireAdmin: true,
+		})
+		registerSetting(settingModule{
+			Name:         "Cluster Info",
+			Desc:         "Health of this node, its peers, replication and scheduling",
+			IconPath:     "SystemAO/cluster/img/small_icon.png",
+			Group:        "Cluster",
+			StartDir:     "SystemAO/cluster/clusterinfo.html",
+			RequireAdmin: true,
+		})
 
 		adminRouter := prout.NewModuleRouter(prout.RouterOption{
 			ModuleName:  "System Setting",
@@ -463,13 +521,15 @@ func ClusterInit() {
 					if prevChange != nil {
 						prevChange(in)
 					}
-					if in {
-						clusterMountDrive()
-					} else {
-						clusterUnmountDrive()
+					clusterSyncDrive()
+				}
+				//Show or hide cluster:/ as volumes come and go
+				clusterMetadata.OnChange = func(kind string, payload []byte) {
+					if kind == metadata.KindVolume {
+						go clusterSyncDrive()
 					}
 				}
-				clusterMountDrive()
+				clusterSyncDrive()
 
 				//Event bus: file / replica / node events, script hooks, web feed
 				bus, err := events.New(clusterManager, clusterRunHook)
@@ -595,16 +655,13 @@ func ClusterInit() {
 			}
 		}
 
-		registerSetting(settingModule{
-			Name:         "Cluster",
-			Desc:         "Create or join an ArozOS cluster",
-			IconPath:     "SystemAO/cluster/img/small_icon.png",
-			Group:        "Cluster",
-			StartDir:     "SystemAO/cluster/cluster.html",
-			RequireAdmin: true,
-		})
 	}
 
+}
+
+// clusterStartNeighbourhood runs the older mDNS neighbour discovery, which is
+// separate from the cluster and governed by -allow_mdns.
+func clusterStartNeighbourhood() {
 	//Only enable neighbourhood scanning on mdns enabled mode
 	if *allow_mdns && MDNS != nil {
 		//Start the network discovery

+ 3 - 3
src/file_system.go

@@ -1103,7 +1103,7 @@ func system_fs_validateFileOpr(w http.ResponseWriter, r *http.Request) {
 
 	//Loop through all files are see if there are duplication during copy and paste
 	sourceFiles := []string{}
-	decodedSourceFiles, _ := url.QueryUnescape(vsrcFiles)
+	decodedSourceFiles := system_fs_specialURIDecode(vsrcFiles)
 	err = json.Unmarshal([]byte(decodedSourceFiles), &sourceFiles)
 	if err != nil {
 		utils.SendErrorResponse(w, "Source file JSON parse error.")
@@ -1947,7 +1947,7 @@ func parseFileOperationRequest(operation string, vsrcFiles string, vdestFile str
 
 	//Decode the source file list
 	var sourceFiles []string
-	decodedSourceFiles, _ := url.QueryUnescape(vsrcFiles)
+	decodedSourceFiles := system_fs_specialURIDecode(vsrcFiles)
 	err := json.Unmarshal([]byte(decodedSourceFiles), &sourceFiles)
 	if err != nil {
 		systemWideLogger.PrintAndLog("File System", "File operation source file JSON parse error", err)
@@ -1967,7 +1967,7 @@ func parseFileOperationRequest(operation string, vsrcFiles string, vdestFile str
 	vdestFile = strings.ReplaceAll(vdestFile, "{{plug_sign}}", "+")
 
 	//Decode the target position
-	escapedVdest, _ := url.QueryUnescape(vdestFile)
+	escapedVdest := system_fs_specialURIDecode(vdestFile)
 	vdestFile = escapedVdest
 
 	if vdestFile == "" {

+ 2 - 1
src/flags.go

@@ -35,7 +35,7 @@ var subserviceBasePort = 12810            //Next subservice port
 
 // =========== SYSTEM BUILD INFORMATION ==============
 var build_version = "development"                      //System build flag, this can be either {development / production / stable}
-var internal_version = "3.0.3"                         //Internal build version, [fork_id].[major_release_no].[minor_release_no]
+var internal_version = "3.0.4"                         //Internal build version, [fork_id].[major_release_no].[minor_release_no]
 var deviceUUID string                                  //The device uuid of this host
 var deviceVendor = "IMUSLAB.INC"                       //Vendor of the system
 var deviceVendorURL = "http://imuslab.com"             //Vendor contact information
@@ -59,6 +59,7 @@ var show_version = flag.Bool("version", false, "Show system build version")
 var host_name = flag.String("hostname", "My ArOZ", "Default name for this host")
 var system_uuid = flag.String("uuid", "", "System UUID for clustering and distributed computing. Only need to config once for first time startup. Leave empty for auto generation.")
 var disable_subservices = flag.Bool("disable_subservice", false, "Disable subservices completely")
+var disable_cluster = flag.Bool("disable_cluster", false, "Disable the cluster features (joining, the cluster:/ drive, cluster jobs and their settings pages)")
 
 // Flags related to Networking
 var allow_upnp = flag.Bool("allow_upnp", false, "Enable uPNP service, recommended for host under NAT router")

+ 2 - 1
src/main.router.go

@@ -68,7 +68,8 @@ func mrouter(h http.Handler) http.Handler {
 			//Cluster node-to-node protocol (ACN). Requests are authenticated by
 			//node signatures, not user sessions, see mod/cluster/acn
 			if clusterManager == nil {
-				errorHandleInternalServerError(w, r)
+				//Cluster disabled (-disable_cluster) or its agent failed to start
+				http.NotFound(w, r)
 				return
 			}
 			clusterManager.ACNHandler().ServeHTTP(w, r)

+ 60 - 10
src/mod/cluster/README.md

@@ -186,19 +186,34 @@ master and can always be rebuilt from the real files (storage reconcile).
   online peers (`meta/append`). Followers submit through the leader
   (`meta/submit`) or queue in `meta_pending` until one is reachable. Gaps
   are filled by `meta/log?after=N`; a compacted range or a new term triggers
-  a full `meta/snapshot`.
+  a full `meta/snapshot`. Only one goroutine drains the pending queue at a
+  time; other callers ask it for another round.
+- **Persistence** (`persist.go`): the store serves everything from memory
+  and writes its disk copy behind. A change updates memory under the store
+  lock and queues its disk write; queued writes are coalesced per key and
+  committed in one `database.WriteBatch` transaction every 50 ms, and
+  `Close` commits the rest. No disk sync ever happens under the store lock,
+  and the lease has its own lock, so a burst of writes cannot starve lease
+  renewal. A crash loses at most the last flush interval of this node's disk
+  copy, which catch-up or a snapshot restores like any other gap. Reading
+  the log back (`meta/log`, compaction) flushes first.
 - Tables (`meta_*`) are registered with `membership.RegisterClusterTable`
-  and wiped when the node leaves.
+  and wiped when the node leaves; the old store's queued writes are dropped
+  then, so they cannot bring wiped records back.
 
 Admin API: `/system/cluster/meta/{status,ls,stat,policy/list,policy/set}`.
 
 ## Storage (ACS) and the `cluster:/` drive
 
 Package `storage/` plus the file-system backend
-`mod/filesystem/abstractions/clusterfs`. When a node is in a cluster the core
-mounts a `Cluster` drive (`cluster:/`, public hierarchy, buffered) into the
-base storage pool, so File Manager, WebDAV, media serving and the AGI
-`filelib` use it like any other drive.
+`mod/filesystem/abstractions/clusterfs`. When a node is in a cluster **and
+the cluster has at least one volume**, the core mounts a `Cluster` drive
+(`cluster:/`, public hierarchy, buffered) into the base storage pool, so File
+Manager, WebDAV, media serving and the AGI `filelib` use it like any other
+drive. Without a cluster or without a volume the drive is unmounted, so it is
+missing from File Manager and from everything that walks the mounted drives
+(nightly tasks included). `clusterSyncDrive` in `src/cluster.go` re-checks on
+every membership change and every volume record change.
 
 - **Volumes**: an admin contributes a folder of a local drive
   (`storage/volume/add`, e.g. `user:/cluster`). Files in the namespace are
@@ -216,7 +231,14 @@ base storage pool, so File Manager, WebDAV, media serving and the AGI
   whole-file SHA-256 at commit, resumable, `.part-<session>` files renamed
   into place, sized for Cloudflare's request limits.
 
-Admin API: `/system/cluster/storage/{status,volume/add,volume/remove,volume/readonly,rescan}`.
+- **Nearly full volumes**: below 5 % free a volume goes read only
+  (`LowSpace`) and stops taking new files, and it recovers above 7 %. The
+  cluster-wide setting `storage.autoReadOnly` (Cluster Settings > Storage)
+  turns this off; volumes the guard had locked become writable again at
+  each node's next volume refresh (within a minute). Read only set by an
+  admin is never touched.
+
+Admin API: `/system/cluster/storage/{status,volume/add,volume/remove,volume/readonly,rescan,autoreadonly}`.
 
 ## Replication
 
@@ -271,6 +293,12 @@ status queries locally and a new leader resumes scheduling.
   `job.log()`, `job.progress()`, `job.cancelled()` and `job.abortIfCancelled()`,
   runs it as the owner through the AGI gateway, and publishes the return value
   as the job output. At most `NumCPU()` jobs run per node; the rest wait.
+- **Dispatch once**: scheduling passes are serialised, so two passes never
+  both see a job as queued and hand it out twice, and a node accepts each
+  job once even if a hand-over is repeated. A node that answers a hand-over
+  with a temporary error (a tunnel still connecting after a restart, not
+  yet knowing the leader) is retried; only "the owner has no account here"
+  or "this node cannot run jobs" keeps it away from that job for good.
 - **Leases**: a running node refreshes the job lease every 10 s. If it goes
   silent the leader requeues the job, and fails it after `MaxAttempts`.
   Cancellation marks the record; the running node interrupts its VM.
@@ -351,7 +379,8 @@ volume.
 Endpoints: `/system/cluster/sched/status` (weights, a per-node score preview
 and the latency matrix), `sched/weights` (GET / POST, any subset of the fields
 or `reset=true`) and `sched/explain?job=<id>`. UI: the Scheduling card on the
-Cluster page, with the weight sliders, the score preview and the matrix.
+Cluster Settings page (weight sliders) and the Cluster Info page (score
+preview, latency matrix and the explain box).
 
 Weights are read back through the defaults, so a record written by an older
 version keeps the shipped value for a weight it never knew about.
@@ -360,8 +389,29 @@ version keeps the shipped value for a weight it never knew about.
 
 `status`, `create`, `join`, `leave`, `config`, `testurl`, `token/new`,
 `token/list`, `token/revoke`, `node/remove`, `node/state`, `node/probe`,
-`nodes`, `capabilities`, `sched/{status,weights,explain}`. The UI is
-[`web/SystemAO/cluster/cluster.html`](../../web/SystemAO/cluster/cluster.html).
+`nodes`, `capabilities`, `sched/{status,weights,explain}`.
+
+The UI is three System Settings tabs in the Cluster group:
+
+| Tab | Page | What it holds |
+|---|---|---|
+| Cluster Settings | [`cluster.html`](../../web/SystemAO/cluster/cluster.html) | this node's name and URL, create / join / leave, join tokens, identity origin, volumes and the nearly-full toggle, replica policies, member maintenance and draining, scheduling weights |
+| Cluster Info | [`clusterinfo.html`](../../web/SystemAO/cluster/clusterinfo.html) | this node's reachability, platform and health, the cluster summary and namespace state, the node table with probes, replication health and tools, scheduling scores, the latency matrix and the job explainer |
+| Cluster Jobs | [`jobs.html`](../../web/SystemAO/cluster/jobs.html) | submit, follow and cancel jobs |
+
+They share `cluster.css` and `cluster.common.js`. Every string is localized
+through [`web/SystemAO/locale/cluster.json`](../../web/SystemAO/locale/cluster.json)
+(en-us, zh-tw, zh-hk, zh-cn, ja-jp, ko-kr): static labels carry a `locale`
+attribute, scripts call `CL.t(key, fallback)`, and messages that come back
+from the server go through `CL.tr(message)`, which matches `msg/<text>`
+entries exactly and `msgp/<template>` entries with `{0}`-style parts (each
+part translated again). When you add a server message that reaches these
+pages, add it to the locale file too; untranslated messages simply stay in
+English.
+
+Start ArozOS with `-disable_cluster` to leave the whole feature off: no
+cluster agent, no `cluster:/` drive, no cluster tabs, and `/cluster/acn/*`
+answers 404.
 
 ## Roadmap
 

+ 31 - 2
src/mod/cluster/metadata/log.go

@@ -236,10 +236,16 @@ func (mgr *Manager) applySnapshot(snap Snapshot) {
 		}
 	}
 	for i := range snap.Volumes {
-		mgr.st.putVolume(&snap.Volumes[i])
+		if mgr.st.putVolume(&snap.Volumes[i]) && mgr.OnChange != nil {
+			js, _ := json.Marshal(snap.Volumes[i])
+			mgr.OnChange(KindVolume, js)
+		}
 	}
 	for i := range snap.Policies {
-		mgr.st.putPolicy(&snap.Policies[i])
+		if mgr.st.putPolicy(&snap.Policies[i]) && mgr.OnChange != nil {
+			js, _ := json.Marshal(snap.Policies[i])
+			mgr.OnChange(KindPolicy, js)
+		}
 	}
 	for i := range snap.Jobs {
 		if mgr.st.putJob(&snap.Jobs[i]) && mgr.OnChange != nil {
@@ -274,7 +280,30 @@ func (mgr *Manager) pendingLoop() {
 
 // flushPending hands queued entries to the leader (or accepts them locally
 // when this node became the leader meanwhile).
+// flushPending drains the pending queue. It is called after every local
+// write, so only one call does the work at a time: a second call just asks
+// the running one for another round. Many concurrent drains each walking the
+// whole queue made the work grow with the square of the backlog.
 func (mgr *Manager) flushPending() {
+	mgr.flushAgain.Store(true)
+	for {
+		if !mgr.flushing.CompareAndSwap(false, true) {
+			return //the active drain will see flushAgain
+		}
+		for mgr.flushAgain.Swap(false) {
+			mgr.drainPending()
+		}
+		mgr.flushing.Store(false)
+		//A request that arrived as we were finishing gets its round now
+		if !mgr.flushAgain.Load() {
+			return
+		}
+	}
+}
+
+// drainPending sends every queued change to the leader, or accepts it when
+// this node is the leader.
+func (mgr *Manager) drainPending() {
 	pending := mgr.st.pendingEntries()
 	if len(pending) == 0 {
 		return

+ 15 - 1
src/mod/cluster/metadata/manager.go

@@ -12,6 +12,7 @@ package metadata
 import (
 	"errors"
 	"sync"
+	"sync/atomic"
 	"time"
 
 	"imuslab.com/arozos/mod/cluster/membership"
@@ -41,6 +42,11 @@ type Manager struct {
 	catchupCh chan struct{}
 	appends   int
 
+	//flushing is set while one goroutine drains the pending queue; the
+	//others only raise flushAgain so the active one does another round.
+	flushing   atomic.Bool
+	flushAgain atomic.Bool
+
 	//OnChange fires after a record changed locally or by replication.
 	OnChange func(kind string, payload []byte)
 }
@@ -104,12 +110,20 @@ func New(opt Option) (*Manager, error) {
 func (mgr *Manager) Close() {
 	mgr.once.Do(func() { close(mgr.stop) })
 	mgr.wg.Wait()
+	mgr.st.close()
 }
 
 // resetLocal forgets everything after the node left the cluster (the tables
 // were already wiped by membership; the in-memory maps must follow).
 func (mgr *Manager) resetLocal() {
-	mgr.st = newStore(mgr.m.DB())
+	//Drop the old store's queued disk writes, then wipe the tables again in
+	//case one of them landed between membership's wipe and this call
+	mgr.st.discard()
+	db := mgr.m.DB()
+	for _, t := range Tables {
+		db.DropTable(t)
+	}
+	mgr.st = newStore(db)
 }
 
 /*

+ 184 - 0
src/mod/cluster/metadata/persist.go

@@ -0,0 +1,184 @@
+package metadata
+
+/*
+	Write-behind persistence for the metadata store.
+
+	The store keeps everything it serves in memory; the disk copy exists so
+	a node can restart without a full resync. Writing that copy inline, one
+	synced transaction per change while holding the store lock, made every
+	reader and writer queue behind the disk. Under a burst the lease renewal
+	waited long enough for the leader to lose its lease, and the node never
+	caught up.
+
+	So a change now updates memory and queues its disk write here. Queued
+	writes are coalesced per key (only the newest value of a key is written)
+	and a background writer commits the whole queue in one transaction every
+	FlushInterval. Close flushes what is left.
+
+	A crash can lose the last FlushInterval of changes from this node's disk
+	copy. That copy is not authoritative: the replicated log and snapshots
+	bring a restarted node back up to date, exactly as they do for a node
+	that was offline.
+*/
+
+import (
+	"encoding/json"
+	"sync"
+	"time"
+
+	"imuslab.com/arozos/mod/database"
+	"imuslab.com/arozos/mod/info/logger"
+)
+
+// FlushInterval is how often queued writes reach the disk.
+var FlushInterval = 50 * time.Millisecond
+
+type opKey struct{ table, key string }
+
+type persister struct {
+	db       *database.Database
+	interval time.Duration //FlushInterval when the persister was made
+
+	mu      sync.Mutex
+	queue   map[opKey]database.BatchOp
+	stopped bool
+
+	flushMu sync.Mutex //one commit at a time, so older values never land last
+	once    sync.Once  //close or discard, whichever comes first
+	wake    chan struct{}
+	stop    chan struct{}
+	done    chan struct{}
+}
+
+func newPersister(db *database.Database) *persister {
+	p := &persister{
+		db:       db,
+		interval: FlushInterval,
+		queue:    map[opKey]database.BatchOp{},
+		wake:     make(chan struct{}, 1),
+		stop:     make(chan struct{}),
+		done:     make(chan struct{}),
+	}
+	go p.loop()
+	return p
+}
+
+// put queues a write. The value is marshalled now, so the caller may keep
+// changing its own copy after put returns.
+func (p *persister) put(table string, key string, value interface{}) {
+	js, err := json.Marshal(value)
+	if err != nil {
+		logger.PrintAndLog("Cluster", "Metadata "+table+"/"+key+" could not be encoded", err)
+		return
+	}
+	p.enqueue(database.BatchOp{Table: table, Key: key, Value: json.RawMessage(js)})
+}
+
+// del queues a removal.
+func (p *persister) del(table string, key string) {
+	p.enqueue(database.BatchOp{Table: table, Key: key, Delete: true})
+}
+
+func (p *persister) enqueue(op database.BatchOp) {
+	p.mu.Lock()
+	if p.stopped {
+		p.mu.Unlock()
+		return
+	}
+	p.queue[opKey{op.Table, op.Key}] = op //newest change of a key wins
+	p.mu.Unlock()
+	select {
+	case p.wake <- struct{}{}:
+	default:
+	}
+}
+
+func (p *persister) loop() {
+	defer close(p.done)
+	for {
+		select {
+		case <-p.stop:
+			return
+		case <-p.wake:
+		}
+		//Let a burst gather so it shares one transaction
+		select {
+		case <-p.stop:
+			return
+		case <-time.After(p.interval):
+		}
+		p.flush()
+	}
+}
+
+// flush commits everything queued so far and returns once it is on disk.
+func (p *persister) flush() {
+	p.flushMu.Lock()
+	defer p.flushMu.Unlock()
+
+	p.mu.Lock()
+	if len(p.queue) == 0 {
+		p.mu.Unlock()
+		return
+	}
+	batch := p.queue
+	p.queue = map[opKey]database.BatchOp{}
+	p.mu.Unlock()
+
+	ops := make([]database.BatchOp, 0, len(batch))
+	for _, op := range batch {
+		ops = append(ops, op)
+	}
+	if err := p.db.WriteBatch(ops); err != nil {
+		logger.PrintAndLog("Cluster", "Metadata could not be written to disk; retrying", err)
+		//Put the batch back unless a newer change of the same key arrived
+		p.mu.Lock()
+		if !p.stopped {
+			for k, op := range batch {
+				if _, newer := p.queue[k]; !newer {
+					p.queue[k] = op
+				}
+			}
+		}
+		p.mu.Unlock()
+		select {
+		case p.wake <- struct{}{}:
+		default:
+		}
+	}
+}
+
+// pending reports how many keys wait to be written (for tests and status).
+func (p *persister) pending() int {
+	p.mu.Lock()
+	defer p.mu.Unlock()
+	return len(p.queue)
+}
+
+// close stops the writer and commits what is left.
+func (p *persister) close() {
+	p.once.Do(func() {
+		close(p.stop)
+		<-p.done
+		p.mu.Lock()
+		p.stopped = true
+		p.mu.Unlock()
+		p.flush()
+	})
+}
+
+// discard stops the writer and drops what is queued, for a store whose
+// tables were wiped: writing its leftovers would bring the old data back.
+func (p *persister) discard() {
+	p.once.Do(func() {
+		p.mu.Lock()
+		p.stopped = true
+		p.queue = map[opKey]database.BatchOp{}
+		p.mu.Unlock()
+		close(p.stop)
+		<-p.done
+		//Wait out a commit another caller had already started
+		p.flushMu.Lock()
+		p.flushMu.Unlock()
+	})
+}

+ 158 - 0
src/mod/cluster/metadata/persist_test.go

@@ -0,0 +1,158 @@
+package metadata
+
+import (
+	"encoding/json"
+	"path/filepath"
+	"sync"
+	"testing"
+	"time"
+
+	"imuslab.com/arozos/mod/database"
+)
+
+func persistDB(t *testing.T) *database.Database {
+	t.Helper()
+	db, err := database.NewDatabase(filepath.Join(t.TempDir(), "persist.db"), false)
+	if err != nil {
+		t.Fatalf("NewDatabase: %v", err)
+	}
+	t.Cleanup(db.Close)
+	db.NewTable("t")
+	return db
+}
+
+func readInt(t *testing.T, db *database.Database, key string) (int, bool) {
+	t.Helper()
+	if !db.KeyExists("t", key) {
+		return 0, false
+	}
+	var v int
+	if err := db.Read("t", key, &v); err != nil {
+		t.Fatalf("Read(%s): %v", key, err)
+	}
+	return v, true
+}
+
+func TestPersisterWritesBehind(t *testing.T) {
+	db := persistDB(t)
+	p := newPersister(db)
+	defer p.close()
+
+	p.put("t", "a", 1)
+	p.put("t", "a", 2) //coalesced: only the newest value is written
+	p.put("t", "b", 1)
+	p.del("t", "b") //a put then a delete of one key is just a delete
+	if p.pending() != 2 {
+		t.Errorf("two keys should be queued, got %d", p.pending())
+	}
+	p.flush()
+	if p.pending() != 0 {
+		t.Errorf("flush should empty the queue, %d left", p.pending())
+	}
+	if v, ok := readInt(t, db, "a"); !ok || v != 2 {
+		t.Errorf("a = %v (%v), want 2", v, ok)
+	}
+	if db.KeyExists("t", "b") {
+		t.Errorf("b should have been deleted")
+	}
+
+	//The background writer commits without an explicit flush
+	p.put("t", "c", 3)
+	deadline := time.Now().Add(2 * time.Second)
+	for time.Now().Before(deadline) {
+		if _, ok := readInt(t, db, "c"); ok {
+			return
+		}
+		time.Sleep(10 * time.Millisecond)
+	}
+	t.Errorf("the background writer never committed c")
+}
+
+func TestPersisterCapturesValueAtPut(t *testing.T) {
+	db := persistDB(t)
+	p := newPersister(db)
+	defer p.close()
+	v := map[string]int{"n": 1}
+	p.put("t", "m", v)
+	v["n"] = 99 //changing the caller's copy afterwards must not leak into the write
+	p.flush()
+	var got map[string]int
+	db.Read("t", "m", &got)
+	if got["n"] != 1 {
+		t.Errorf("stored %v, want the value as it was at put", got)
+	}
+}
+
+func TestPersisterCloseAndDiscard(t *testing.T) {
+	tests := []struct {
+		name      string
+		finish    func(p *persister)
+		wantSaved bool
+	}{
+		{"close commits what is queued", func(p *persister) { p.close() }, true},
+		{"discard drops what is queued", func(p *persister) { p.discard() }, false},
+	}
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			db := persistDB(t)
+			old := FlushInterval
+			FlushInterval = time.Hour //keep the background writer out of the way
+			p := newPersister(db)
+			FlushInterval = old
+
+			p.put("t", "k", 7)
+			tc.finish(p)
+			if _, ok := readInt(t, db, "k"); ok != tc.wantSaved {
+				t.Errorf("saved = %v, want %v", ok, tc.wantSaved)
+			}
+			//After either, further changes are ignored and both calls are safe to repeat
+			p.put("t", "late", 1)
+			p.close()
+			p.discard()
+			if db.KeyExists("t", "late") {
+				t.Errorf("a stopped persister must not write")
+			}
+		})
+	}
+}
+
+func TestPersisterRetriesAFailedWrite(t *testing.T) {
+	db := persistDB(t)
+	p := newPersister(db)
+	defer p.close()
+
+	db.UpdateReadWriteMode(true) //every write fails
+	p.put("t", "r", 1)
+	p.flush()
+	if p.pending() != 1 {
+		t.Fatalf("a failed write must stay queued, %d queued", p.pending())
+	}
+	db.UpdateReadWriteMode(false)
+	p.flush()
+	if v, ok := readInt(t, db, "r"); !ok || v != 1 {
+		t.Errorf("the retried write did not land: %v %v", v, ok)
+	}
+}
+
+func TestPersisterConcurrentPuts(t *testing.T) {
+	db := persistDB(t)
+	p := newPersister(db)
+	var wg sync.WaitGroup
+	for i := 0; i < 50; i++ {
+		wg.Add(1)
+		go func(i int) {
+			defer wg.Done()
+			js, _ := json.Marshal(i)
+			p.put("t", string(rune('a'+i%26))+string(js), i)
+		}(i)
+	}
+	wg.Wait()
+	p.close()
+	rows, err := db.ListTable("t")
+	if err != nil {
+		t.Fatalf("ListTable: %v", err)
+	}
+	if len(rows) != 50 {
+		t.Errorf("want 50 rows after close, got %d", len(rows))
+	}
+}

+ 44 - 28
src/mod/cluster/metadata/store.go

@@ -43,8 +43,9 @@ const (
 var Tables = []string{tableFiles, tablePaths, tableVolumes, tablePolicy, tableJobs, tableSettings, tableLog, tablePending, tableState}
 
 type store struct {
-	db *database.Database
-	mu sync.RWMutex
+	db      *database.Database
+	persist *persister //disk writes happen here, never under mu
+	mu      sync.RWMutex
 
 	files    map[string]*FileRecord
 	paths    map[string]string
@@ -57,6 +58,10 @@ type store struct {
 	lastSeq uint64
 	applied uint64
 	term    uint64
+
+	//The lease has its own lock: renewing it must never wait behind the
+	//namespace, or a busy leader loses its lease.
+	leaseMu sync.RWMutex
 	lease   Lease
 }
 
@@ -77,9 +82,20 @@ func newStore(db *database.Database) *store {
 		db.NewTable(t)
 	}
 	s.load()
+	s.persist = newPersister(db)
 	return s
 }
 
+// close commits pending disk writes and stops the writer.
+func (s *store) close() {
+	s.persist.close()
+}
+
+// discard drops pending disk writes; used when the tables were wiped.
+func (s *store) discard() {
+	s.persist.discard()
+}
+
 func (s *store) load() {
 	if entries, err := s.db.ListTable(tableFiles); err == nil {
 		for _, kv := range entries {
@@ -162,25 +178,25 @@ func (s *store) putFile(in *FileRecord) bool {
 	}
 	if ok && existing.Path != rec.Path && s.paths[existing.Path] == rec.ID {
 		delete(s.paths, existing.Path)
-		s.db.Delete(tablePaths, existing.Path)
+		s.persist.del(tablePaths, existing.Path)
 	}
 	s.files[rec.ID] = rec
-	s.db.Write(tableFiles, rec.ID, rec)
+	s.persist.put(tableFiles, rec.ID, rec)
 	if rec.Removed {
 		if s.paths[rec.Path] == rec.ID {
 			delete(s.paths, rec.Path)
-			s.db.Delete(tablePaths, rec.Path)
+			s.persist.del(tablePaths, rec.Path)
 		}
 	} else {
 		//A newer record for the same path replaces an older one's claim
 		if otherID, taken := s.paths[rec.Path]; taken && otherID != rec.ID {
 			if other, ok := s.files[otherID]; ok && other.Version < rec.Version {
 				s.paths[rec.Path] = rec.ID
-				s.db.Write(tablePaths, rec.Path, rec.ID)
+				s.persist.put(tablePaths, rec.Path, rec.ID)
 			}
 		} else {
 			s.paths[rec.Path] = rec.ID
-			s.db.Write(tablePaths, rec.Path, rec.ID)
+			s.persist.put(tablePaths, rec.Path, rec.ID)
 		}
 	}
 	return true
@@ -300,7 +316,7 @@ func (s *store) putVolume(in *Volume) bool {
 	}
 	v := *in
 	s.volumes[v.ID] = &v
-	s.db.Write(tableVolumes, v.ID, v)
+	s.persist.put(tableVolumes, v.ID, v)
 	return true
 }
 
@@ -338,7 +354,7 @@ func (s *store) putPolicy(in *Policy) bool {
 		return false
 	}
 	s.policies[p.Folder] = &p
-	s.db.Write(tablePolicy, p.Folder, p)
+	s.persist.put(tablePolicy, p.Folder, p)
 	return true
 }
 
@@ -378,7 +394,7 @@ func (s *store) putJob(in *Job) bool {
 	}
 	j := *in
 	s.jobs[j.ID] = &j
-	s.db.Write(tableJobs, j.ID, j)
+	s.persist.put(tableJobs, j.ID, j)
 	return true
 }
 
@@ -412,7 +428,7 @@ func (s *store) gcJobs(cutoff int64, keepStatus map[string]bool) int {
 	for id, j := range s.jobs {
 		if j.Created < cutoff && !keepStatus[j.Status] {
 			delete(s.jobs, id)
-			s.db.Delete(tableJobs, id)
+			s.persist.del(tableJobs, id)
 			n++
 		}
 	}
@@ -430,7 +446,7 @@ func (s *store) putSetting(in *Setting) bool {
 	}
 	st := *in
 	s.settings[st.Key] = &st
-	s.db.Write(tableSettings, st.Key, st)
+	s.persist.put(tableSettings, st.Key, st)
 	return true
 }
 
@@ -456,15 +472,16 @@ func (s *store) appendLog(e Entry, term uint64) Entry {
 	s.lastSeq++
 	e.Seq = s.lastSeq
 	e.Term = term
-	s.db.Write(tableLog, logKey(e.Seq), e)
+	s.persist.put(tableLog, logKey(e.Seq), e)
 	return e
 }
 
 // logAfter returns up to max entries with Seq > after. ok is false when the
 // requested range was compacted away.
 func (s *store) logAfter(after uint64, max int) ([]Entry, bool) {
-	s.mu.RLock()
-	defer s.mu.RUnlock()
+	//The log is read back from disk, so commit what is still queued first
+	s.persist.flush()
+	lastSeq := s.getLastSeq()
 	entries, err := s.db.ListTableWithPrefix(tableLog, "")
 	if err != nil {
 		return nil, false
@@ -486,7 +503,7 @@ func (s *store) logAfter(after uint64, max int) ([]Entry, bool) {
 			}
 		}
 	}
-	if after > 0 && (first > after+1 || (len(entries) == 0 && s.lastSeq > after)) {
+	if after > 0 && (first > after+1 || (len(entries) == 0 && lastSeq > after)) {
 		return nil, false
 	}
 	return out, true
@@ -494,14 +511,13 @@ func (s *store) logAfter(after uint64, max int) ([]Entry, bool) {
 
 // compactLog keeps only the newest keepLast entries.
 func (s *store) compactLog(keepLast int) {
-	s.mu.Lock()
-	defer s.mu.Unlock()
+	s.persist.flush()
 	entries, err := s.db.ListTableWithPrefix(tableLog, "")
 	if err != nil || len(entries) <= keepLast {
 		return
 	}
 	for _, kv := range entries[:len(entries)-keepLast] {
-		s.db.Delete(tableLog, string(kv[0]))
+		s.persist.del(tableLog, string(kv[0]))
 	}
 }
 
@@ -530,35 +546,35 @@ func (s *store) setApplied(seq uint64, term uint64) {
 	defer s.mu.Unlock()
 	s.applied = seq
 	s.term = term
-	s.db.Write(tableState, stateApplied, seq)
-	s.db.Write(tableState, stateTerm, term)
+	s.persist.put(tableState, stateApplied, seq)
+	s.persist.put(tableState, stateTerm, term)
 }
 
 func (s *store) getLease() Lease {
-	s.mu.RLock()
-	defer s.mu.RUnlock()
+	s.leaseMu.RLock()
+	defer s.leaseMu.RUnlock()
 	return s.lease
 }
 
 func (s *store) setLease(l Lease) {
-	s.mu.Lock()
-	defer s.mu.Unlock()
+	s.leaseMu.Lock()
 	s.lease = l
-	s.db.Write(tableState, stateLease, l)
+	s.leaseMu.Unlock()
+	s.persist.put(tableState, stateLease, l)
 }
 
 func (s *store) addPending(id string, e Entry) {
 	s.mu.Lock()
 	defer s.mu.Unlock()
 	s.pending[id] = e
-	s.db.Write(tablePending, id, e)
+	s.persist.put(tablePending, id, e)
 }
 
 func (s *store) removePending(id string) {
 	s.mu.Lock()
 	defer s.mu.Unlock()
 	delete(s.pending, id)
-	s.db.Delete(tablePending, id)
+	s.persist.del(tablePending, id)
 }
 
 func (s *store) pendingEntries() map[string]Entry {

+ 24 - 5
src/mod/cluster/storage/handlers_admin.go

@@ -30,10 +30,11 @@ type VolumeView struct {
 
 // StatusView is the storage picture for the settings page.
 type StatusView struct {
-	Ready      bool         `json:"ready"`
-	Volumes    []VolumeView `json:"volumes"`
-	LocalRoots []RootView   `json:"localRoots"`
-	Sessions   int          `json:"sessions"`
+	Ready        bool         `json:"ready"`
+	AutoReadOnly bool         `json:"autoReadOnly"` //nearly full volumes become read only
+	Volumes      []VolumeView `json:"volumes"`
+	LocalRoots   []RootView   `json:"localRoots"`
+	Sessions     int          `json:"sessions"`
 }
 
 // RootView is a local drive the admin may contribute a folder from.
@@ -44,7 +45,7 @@ type RootView struct {
 
 // Status builds the storage status.
 func (s *Service) Status() StatusView {
-	st := StatusView{Ready: s.Ready(), Volumes: []VolumeView{}, LocalRoots: []RootView{}}
+	st := StatusView{Ready: s.Ready(), AutoReadOnly: s.AutoReadOnly(), Volumes: []VolumeView{}, LocalRoots: []RootView{}}
 	me := s.m.NodeID()
 	for _, v := range s.meta.Volumes() {
 		if v.Removed {
@@ -137,4 +138,22 @@ func (s *Service) RegisterAdminRoutes(register func(pattern string, handler func
 	register("/system/cluster/storage/volume/remove", s.HandleVolumeRemove)
 	register("/system/cluster/storage/volume/readonly", s.HandleVolumeReadOnly)
 	register("/system/cluster/storage/rescan", s.HandleRescan)
+	register("/system/cluster/storage/autoreadonly", s.HandleAutoReadOnly)
+}
+
+// HandleAutoReadOnly reads (GET) or sets (POST enabled=true|false) whether
+// nearly full volumes are made read only automatically.
+func (s *Service) HandleAutoReadOnly(w http.ResponseWriter, r *http.Request) {
+	if r.Method == http.MethodPost {
+		raw, err := utils.PostPara(r, "enabled")
+		if err != nil || (raw != "true" && raw != "false") {
+			utils.SendErrorResponse(w, "enabled must be true or false")
+			return
+		}
+		if err := s.SetAutoReadOnly(raw == "true"); err != nil {
+			utils.SendErrorResponse(w, err.Error())
+			return
+		}
+	}
+	sendJSON(w, map[string]bool{"enabled": s.AutoReadOnly()})
 }

+ 1 - 1
src/mod/cluster/storage/service.go

@@ -50,7 +50,7 @@ const (
 )
 
 var (
-	ErrNoVolume       = errors.New("no cluster volume available; add one in System Settings > Cluster > Storage")
+	ErrNoVolume       = errors.New("no cluster volume available; add one in System Settings > Cluster Settings > Storage")
 	ErrNoHealthyCopy  = errors.New("no healthy copy of this file is reachable right now")
 	ErrNotLocalVolume = errors.New("volume is not on this node")
 	ErrPathEscape     = errors.New("path escapes the volume")

+ 52 - 0
src/mod/cluster/storage/storage_test.go

@@ -475,3 +475,55 @@ func TestSpaceStateHysteresis(t *testing.T) {
 		})
 	}
 }
+
+func TestLowSpaceAction(t *testing.T) {
+	const total = 1000
+	tests := []struct {
+		name      string
+		free      int64
+		wasFull   bool
+		auto      bool
+		wantMark  bool
+		wantClear bool
+	}{
+		{"guard on, plenty of room", 500, false, true, false, false},
+		{"guard on, nearly full is marked", 40, false, true, true, false},
+		{"guard on, already marked stays", 60, true, true, false, false},
+		{"guard on, recovered is released", 80, true, true, false, true},
+		{"guard off, nearly full is left alone", 40, false, false, false, false},
+		{"guard off, earlier mark is released", 40, true, false, false, true},
+	}
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			mark, clear := lowSpaceAction(tc.free, total, tc.wasFull, tc.auto)
+			if mark != tc.wantMark || clear != tc.wantClear {
+				t.Errorf("lowSpaceAction(%d, %d, %v, %v) = %v, %v; want %v, %v",
+					tc.free, total, tc.wasFull, tc.auto, mark, clear, tc.wantMark, tc.wantClear)
+			}
+		})
+	}
+}
+
+func TestAutoReadOnlySetting(t *testing.T) {
+	a := newTestNode(t, "solo")
+	a.m.CreateCluster("Solo")
+	waitFor(t, "lead", 5*time.Second, func() bool { return a.meta.IsLeader() })
+	if !a.svc.AutoReadOnly() {
+		t.Fatalf("the guard must be on by default")
+	}
+	if err := a.svc.SetAutoReadOnly(false); err != nil {
+		t.Fatalf("SetAutoReadOnly(false): %v", err)
+	}
+	if a.svc.AutoReadOnly() {
+		t.Errorf("the guard should be off")
+	}
+	if a.svc.Status().AutoReadOnly != a.svc.AutoReadOnly() {
+		t.Errorf("status must report the setting")
+	}
+	if err := a.svc.SetAutoReadOnly(true); err != nil {
+		t.Fatalf("SetAutoReadOnly(true): %v", err)
+	}
+	if !a.svc.AutoReadOnly() {
+		t.Errorf("the guard should be back on")
+	}
+}

+ 61 - 5
src/mod/cluster/storage/volumes.go

@@ -5,6 +5,7 @@ package storage
 */
 
 import (
+	"encoding/json"
 	"errors"
 	"fmt"
 	"os"
@@ -191,6 +192,7 @@ func (s *Service) SetVolumeReadOnly(id string, ro bool) error {
 
 // refreshVolumes republishes free space when it moved by more than 1 %.
 func (s *Service) refreshVolumes() {
+	auto := s.AutoReadOnly()
 	for _, v := range s.LocalVolumes() {
 		root, err := s.volumeRoot(&v)
 		if err != nil {
@@ -205,19 +207,24 @@ func (s *Service) refreshVolumes() {
 			delta = -delta
 		}
 		//A volume under the low water mark stops taking new files until it
-		//recovers, so a node never fills its own disk.
-		isFull, recovered := spaceState(free, total, v.ReadOnly && v.LowSpace)
+		//recovers, so a node never fills its own disk (unless an admin turned
+		//that off for the cluster).
 		wasFull := v.ReadOnly && v.LowSpace
+		mark, clear := lowSpaceAction(free, total, wasFull, auto)
 		changed := total != v.Capacity || (total > 0 && delta*100 > total)
-		if isFull && !wasFull {
+		if mark {
 			v.ReadOnly, v.LowSpace, changed = true, true, true
 			logger.PrintAndLog("Cluster", "Volume "+v.Name+" is nearly full and stops taking new files", nil)
 			if s.OnDiskFull != nil {
 				s.OnDiskFull(v)
 			}
-		} else if recovered {
+		} else if clear {
 			v.ReadOnly, v.LowSpace, changed = false, false, true
-			logger.PrintAndLog("Cluster", "Volume "+v.Name+" has room again and takes new files", nil)
+			if auto {
+				logger.PrintAndLog("Cluster", "Volume "+v.Name+" has room again and takes new files", nil)
+			} else {
+				logger.PrintAndLog("Cluster", "Volume "+v.Name+" takes new files again: automatic read-only is turned off", nil)
+			}
 		}
 		if changed {
 			v.Free, v.Capacity = free, total
@@ -226,6 +233,55 @@ func (s *Service) refreshVolumes() {
 	}
 }
 
+// AutoReadOnlyKey is the replicated setting that turns the nearly-full guard
+// on or off for the whole cluster.
+const AutoReadOnlyKey = "storage.autoReadOnly"
+
+type autoReadOnlySetting struct {
+	Enabled bool `json:"enabled"`
+}
+
+// AutoReadOnly reports whether volumes that are nearly full are made read
+// only automatically. It is on unless an admin turned it off.
+func (s *Service) AutoReadOnly() bool {
+	st, ok := s.meta.Setting(AutoReadOnlyKey)
+	if !ok {
+		return true
+	}
+	var v autoReadOnlySetting
+	if json.Unmarshal(st.Value, &v) != nil {
+		return true
+	}
+	return v.Enabled
+}
+
+// SetAutoReadOnly turns the nearly-full guard on or off for every node. This
+// node applies it at once; the others at their next volume refresh.
+func (s *Service) SetAutoReadOnly(enabled bool) error {
+	js, err := json.Marshal(autoReadOnlySetting{Enabled: enabled})
+	if err != nil {
+		return err
+	}
+	if err := s.meta.Submit(metadata.KindSetting, &metadata.Setting{Key: AutoReadOnlyKey, Value: js}); err != nil {
+		return err
+	}
+	go s.refreshVolumes()
+	return nil
+}
+
+// lowSpaceAction decides what the guard does to one volume. wasFull says the
+// guard itself made the volume read only earlier. With the guard on, a volume
+// is marked below the low water mark and released above the recover mark;
+// with it off, nothing is marked and anything the guard had marked is
+// released, while read-only set by an admin is never touched.
+func lowSpaceAction(free int64, total int64, wasFull bool, auto bool) (mark bool, clear bool) {
+	if !auto {
+		return false, wasFull
+	}
+	full, recovered := spaceState(free, total, wasFull)
+	return full && !wasFull, recovered
+}
+
 // spaceState decides whether a volume is too full to take new files. The two
 // thresholds differ so a volume hovering at the limit does not flap: it stops
 // below diskFullFraction and only starts again above diskRecoverFraction.

+ 28 - 0
src/mod/database/database.go

@@ -97,6 +97,34 @@ func (d *Database) Delete(tableName string, key string) error {
 	return d.delete(tableName, key)
 }
 
+// BatchOp is one change inside a WriteBatch: a Put of Value under Key, or a
+// removal of Key when Delete is true. Value is marshalled to JSON like Write
+// does; a json.RawMessage is stored as it is.
+type BatchOp struct {
+	Table  string
+	Key    string
+	Value  interface{}
+	Delete bool
+}
+
+/*
+WriteBatch applies many changes at once. On the bolt backend they share one
+transaction, so they cost a single disk sync instead of one each, and either
+all of them land or none do. Tables are created when missing. Operations run
+in the order given.
+
+	err := sysdb.WriteBatch([]database.BatchOp{
+		{Table: "MyTable", Key: "a", Value: 1},
+		{Table: "MyTable", Key: "b", Delete: true},
+	})
+*/
+func (d *Database) WriteBatch(ops []BatchOp) error {
+	if len(ops) == 0 {
+		return nil
+	}
+	return d.writeBatch(ops)
+}
+
 /*
 	//List table example usage
 	//Assume the value is stored as a struct named "groupstruct"

+ 87 - 0
src/mod/database/database_batch_test.go

@@ -0,0 +1,87 @@
+package database
+
+import (
+	"encoding/json"
+	"path/filepath"
+	"testing"
+)
+
+func openBatchDB(t *testing.T) *Database {
+	t.Helper()
+	db, err := NewDatabase(filepath.Join(t.TempDir(), "batch.db"), false)
+	if err != nil {
+		t.Fatalf("NewDatabase: %v", err)
+	}
+	t.Cleanup(db.Close)
+	return db
+}
+
+func TestWriteBatch(t *testing.T) {
+	db := openBatchDB(t)
+	db.NewTable("existing")
+	db.Write("existing", "gone", "old")
+
+	err := db.WriteBatch([]BatchOp{
+		{Table: "existing", Key: "a", Value: 1},
+		{Table: "existing", Key: "gone", Delete: true},
+		{Table: "created", Key: "b", Value: map[string]string{"x": "y"}},
+		{Table: "existing", Key: "raw", Value: json.RawMessage(`{"n":2}`)},
+		{Table: "existing", Key: "a", Value: 3}, //later op on the same key wins
+	})
+	if err != nil {
+		t.Fatalf("WriteBatch: %v", err)
+	}
+
+	tests := []struct {
+		name  string
+		table string
+		key   string
+		want  string //JSON of the stored value, "" when it must be absent
+	}{
+		{"put", "existing", "a", "3"},
+		{"delete", "existing", "gone", ""},
+		{"table is created", "created", "b", `{"x":"y"}`},
+		{"raw JSON is stored as is", "existing", "raw", `{"n":2}`},
+	}
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			if tc.want == "" {
+				if db.KeyExists(tc.table, tc.key) {
+					t.Errorf("%s/%s should not exist", tc.table, tc.key)
+				}
+				return
+			}
+			var got json.RawMessage
+			if err := db.Read(tc.table, tc.key, &got); err != nil {
+				t.Fatalf("Read: %v", err)
+			}
+			if string(got) != tc.want {
+				t.Errorf("%s/%s = %s, want %s", tc.table, tc.key, got, tc.want)
+			}
+		})
+	}
+}
+
+func TestWriteBatchEdgeCases(t *testing.T) {
+	db := openBatchDB(t)
+	if err := db.WriteBatch(nil); err != nil {
+		t.Errorf("an empty batch should be a no-op, got %v", err)
+	}
+
+	//A value that cannot be marshalled fails the batch before anything lands
+	err := db.WriteBatch([]BatchOp{
+		{Table: "t", Key: "first", Value: 1},
+		{Table: "t", Key: "bad", Value: make(chan int)},
+	})
+	if err == nil {
+		t.Fatalf("an unmarshallable value must fail the batch")
+	}
+	if db.KeyExists("t", "first") {
+		t.Errorf("a failed batch must not write its other operations")
+	}
+
+	db.UpdateReadWriteMode(true)
+	if err := db.WriteBatch([]BatchOp{{Table: "t", Key: "k", Value: 1}}); err == nil {
+		t.Errorf("a read only database must reject a batch")
+	}
+}

+ 36 - 0
src/mod/database/database_core.go

@@ -158,6 +158,42 @@ func (d *Database) keyExists(tableName string, key string) bool {
 	}
 }
 
+func (d *Database) writeBatch(ops []BatchOp) error {
+	if d.ReadOnly {
+		return errors.New("Operation rejected in ReadOnly mode")
+	}
+	//Marshal first so a bad value fails the batch before anything is written
+	values := make([][]byte, len(ops))
+	for i, op := range ops {
+		if op.Delete {
+			continue
+		}
+		js, err := json.Marshal(op.Value)
+		if err != nil {
+			return err
+		}
+		values[i] = js
+	}
+	return d.Db.(*bolt.DB).Update(func(tx *bolt.Tx) error {
+		for i, op := range ops {
+			b, err := tx.CreateBucketIfNotExists([]byte(op.Table))
+			if err != nil {
+				return err
+			}
+			if op.Delete {
+				if err := b.Delete([]byte(op.Key)); err != nil {
+					return err
+				}
+				continue
+			}
+			if err := b.Put([]byte(op.Key), values[i]); err != nil {
+				return err
+			}
+		}
+		return nil
+	})
+}
+
 func (d *Database) delete(tableName string, key string) error {
 	if d.ReadOnly {
 		return errors.New("Operation rejected in ReadOnly mode")

+ 34 - 0
src/mod/database/database_openwrt.go

@@ -138,6 +138,40 @@ func (d *Database) keyExists(tableName string, key string) bool {
 	return fileExists(entryPath)
 }
 
+// writeBatch has no transactions on this backend; it applies the operations
+// one after another and stops at the first failure.
+func (d *Database) writeBatch(ops []BatchOp) error {
+	if d.ReadOnly {
+		return errors.New("Operation rejected in ReadOnly mode")
+	}
+	//Reject a value that cannot be stored before writing anything
+	for _, op := range ops {
+		if op.Delete {
+			continue
+		}
+		if _, err := json.Marshal(op.Value); err != nil {
+			return err
+		}
+	}
+	for _, op := range ops {
+		if !d.tableExists(op.Table) {
+			if err := d.newTable(op.Table); err != nil {
+				return err
+			}
+		}
+		var err error
+		if op.Delete {
+			err = d.delete(op.Table, op.Key)
+		} else {
+			err = d.write(op.Table, op.Key, op.Value)
+		}
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 func (d *Database) delete(tableName string, key string) error {
 	if d.ReadOnly {
 		return errors.New("Operation rejected in ReadOnly mode")

+ 3 - 4
src/mod/media/mediaserver/mediaserver.go

@@ -102,10 +102,9 @@ func (s *Instance) ValidateSourceFile(w http.ResponseWriter, r *http.Request) (*
 	}
 
 	targetfile, _ := utils.GetPara(r, "file")
-	targetfile, err = url.QueryUnescape(targetfile)
-	if err != nil {
-		return nil, "", "", err
-	}
+	//GetPara has already decoded the query once. Decode again for clients that
+	//double encode, but keep a literal "+" (QueryUnescape would turn it into a space)
+	targetfile = filesystem.DecodeURI(targetfile)
 	if targetfile == "" {
 		return nil, "", "", errors.New("Missing paramter 'file'")
 	}

+ 226 - 0
src/web/SystemAO/cluster/cluster.common.js

@@ -0,0 +1,226 @@
+/*
+    Cluster pages - shared helpers
+
+    Used by Cluster Settings (cluster.html), Cluster Info (clusterinfo.html)
+    and Cluster Jobs (jobs.html). Everything lives on the CL object:
+
+        CL.init(callback)        load ../locale/cluster.json, translate the
+                                 static page, then call callback (also called
+                                 when the locale cannot be loaded)
+        CL.t(key, fallback)      a UI string in the viewer's language
+        CL.tr(message)           a message that came back from the server,
+                                 translated when the locale knows it
+        CL.state(name)           a node / job / copy state label
+
+    Server messages are matched two ways. "msg/<text>" entries translate a
+    fixed message. "msgp/<template>" entries translate a message with
+    variable parts: the template marks each part {0}, {1}, ... and the
+    translation puts them back where that language wants them. Each part is
+    translated again, so "node X could not take the job: <reason>" also
+    translates the reason.
+*/
+
+var CL = (function () {
+    var loc = null;
+    var patterns = null;
+
+    function strings() {
+        if (!loc || !loc.localData || !loc.localData.keys) return null;
+        var set = loc.localData.keys[loc.lang];
+        return (set && set.strings) ? set.strings : null;
+    }
+
+    function t(key, fallback) {
+        var s = strings();
+        if (s && s[key] !== undefined && s[key] !== '') return s[key];
+        return fallback;
+    }
+
+    function escapeRegex(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
+
+    //Compile every "msgp/" template once into a regular expression
+    function compilePatterns() {
+        patterns = [];
+        var s = strings();
+        if (!s) return;
+        Object.keys(s).forEach(function (key) {
+            if (key.indexOf('msgp/') !== 0) return;
+            var template = key.substring(5);
+            var order = [];
+            var parts = template.split(/(\{\d\})/);
+            var re = '^';
+            parts.forEach(function (p, i) {
+                var m = p.match(/^\{(\d)\}$/);
+                if (m) {
+                    order.push(parseInt(m[1], 10));
+                    //The last placeholder takes the rest of the message
+                    re += (i === parts.length - 2 && parts[parts.length - 1] === '') ? '([\\s\\S]*)' : '([\\s\\S]*?)';
+                } else {
+                    re += escapeRegex(p);
+                }
+            });
+            patterns.push({ re: new RegExp(re + '$'), order: order, out: s[key], literal: template.replace(/\{\d\}/g, '').length });
+        });
+        //Try the most specific templates first
+        patterns.sort(function (a, b) { return b.literal - a.literal; });
+    }
+
+    function tr(message, depth) {
+        if (message === null || message === undefined) return message;
+        var text = String(message).trim();
+        if (text === '') return text;
+        depth = depth || 0;
+        var s = strings();
+        if (!s) return text;
+        if (s['msg/' + text] !== undefined && s['msg/' + text] !== '') return s['msg/' + text];
+        if (depth > 3) return text;
+        if (patterns === null) compilePatterns();
+        for (var i = 0; i < patterns.length; i++) {
+            var m = text.match(patterns[i].re);
+            if (!m) continue;
+            var out = patterns[i].out;
+            if (!out) return text;
+            patterns[i].order.forEach(function (slot, idx) {
+                out = out.split('{' + slot + '}').join(tr(m[idx + 1], depth + 1));
+            });
+            return out;
+        }
+        return text;
+    }
+
+    function state(name) {
+        if (!name) return '';
+        return t('cluster/state/' + String(name).toLowerCase(), String(name).toUpperCase());
+    }
+
+    function init(callback) {
+        var done = false;
+        function finish() {
+            if (done) return;
+            done = true;
+            if (callback) callback();
+        }
+        if (typeof NewAppLocale !== 'function' || typeof $ === 'undefined') { finish(); return; }
+        loc = NewAppLocale();
+        loc.init('../locale/cluster.json', function () {
+            patterns = null;
+            try { loc.translate(); } catch (e) {}
+            finish();
+        });
+        //Never hold the page back if the locale file is missing
+        setTimeout(finish, 1500);
+    }
+
+    /* Theme */
+    function applyTheme(rootId) {
+        var root = document.getElementById(rootId);
+        var set = function (isDark) { if (root) root.classList.toggle('dark', isDark); };
+        try {
+            if (typeof ao_module_getSystemThemeColor === 'function') {
+                ao_module_getSystemThemeColor(function (c) { set(c !== 'whiteTheme'); });
+            } else {
+                var theme = null;
+                if (typeof preferredTheme !== 'undefined') theme = preferredTheme;
+                else if (parent && typeof parent.preferredTheme !== 'undefined') theme = parent.preferredTheme;
+                if (theme) set(theme === 'dark' || theme === 'darkTheme');
+            }
+        } catch (e) {}
+        window.detailPageThemeCallback = function (isDark) { set(isDark); };
+    }
+
+    /* Formatting */
+    function esc(s) {
+        return String(s === undefined || s === null ? '' : s)
+            .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
+    }
+    function fmtBytes(b) {
+        if (!b || b <= 0) return '-';
+        var u = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'], i = 0;
+        while (b >= 1024 && i < u.length - 1) { b /= 1024; i++; }
+        return (i === 0 ? b : b.toFixed(1)) + ' ' + u[i];
+    }
+    function fmtAgo(unix) {
+        if (!unix) return t('cluster/never', 'never');
+        var d = Math.max(0, Math.floor(Date.now() / 1000 - unix));
+        if (d < 5) return t('cluster/justnow', 'just now');
+        var n, unit;
+        if (d < 60) { n = d; unit = 's'; }
+        else if (d < 3600) { n = Math.floor(d / 60); unit = 'm'; }
+        else if (d < 86400) { n = Math.floor(d / 3600); unit = 'h'; }
+        else { n = Math.floor(d / 86400); unit = 'd'; }
+        return t('cluster/ago/' + unit, '{0}' + unit + ' ago').replace('{0}', n);
+    }
+    function fmtDate(unix) { return unix ? new Date(unix * 1000).toLocaleString() : '-'; }
+    function bar(pct) {
+        var cls = pct >= 95 ? 'bad' : (pct >= 80 ? 'warn' : '');
+        return '<div class="cl-bar ' + cls + '"><div style="width:' + Math.min(100, Math.max(0, pct)).toFixed(0) + '%"></div></div>';
+    }
+    //Fill {0}, {1}, ... in a translated string
+    function fmt(template) {
+        var args = Array.prototype.slice.call(arguments, 1);
+        return String(template).replace(/\{(\d)\}/g, function (m, i) { return args[i] !== undefined ? args[i] : m; });
+    }
+
+    /* Requests */
+    function apiPost(url, data, cb) {
+        $.ajax({ url: url, method: 'POST', data: data, dataType: 'json' })
+            .done(function (r) { cb(null, r); })
+            .fail(function (xhr) {
+                var msg = xhr.responseText || t('cluster/requestfailed', 'Request failed');
+                try { var j = JSON.parse(xhr.responseText); if (j.error) msg = j.error; } catch (e) {}
+                cb(msg, null);
+            });
+    }
+    function apiResult(r) { return (r && typeof r === 'object' && r.error) ? r.error : null; }
+
+    //Show a message in a .cl-msg / .cj-msg box. Errors from the server are
+    //translated here, so every caller gets it for free.
+    function showMsg(id, text, ok, cls) {
+        var el = document.getElementById(id);
+        if (!el) return;
+        cls = cls || 'cl-msg';
+        el.textContent = ok ? text : tr(text);
+        el.className = cls + ' ' + (ok ? 'ok' : 'err');
+        clearTimeout(el._t);
+        el._t = setTimeout(function () { el.className = cls; }, ok ? 4000 : 8000);
+    }
+
+    /*
+        System Settings loads each tab into its own page with jQuery .load,
+        so relative links would resolve against /SystemAO/system_setting/
+        and timers would outlive the tab. These two helpers handle both.
+    */
+
+    //Open another cluster page: switch tabs inside System Settings, or
+    //follow the link when the page was opened on its own.
+    function openPage(settingName, href) {
+        try {
+            if (typeof loadViaSystemSetting !== 'undefined' && loadViaSystemSetting && typeof openSubcat === 'function' && typeof currentSubcats !== 'undefined' && currentSubcats) {
+                for (var i = 0; i < currentSubcats.length; i++) {
+                    if (currentSubcats[i].Name === settingName) { openSubcat(currentSubcats[i]); return false; }
+                }
+            }
+        } catch (e) {}
+        window.location.href = href;
+        return false;
+    }
+
+    //Run fn every ms while the page root is on screen. Once the tab is
+    //replaced (another settings tab, or this one reopened) the timer stops
+    //itself instead of piling up.
+    function every(fn, ms, rootId) {
+        var root = document.getElementById(rootId);
+        var handle = setInterval(function () {
+            if (!root || !document.body.contains(root)) { clearInterval(handle); return; }
+            fn();
+        }, ms);
+        return handle;
+    }
+
+    return {
+        openPage: openPage, every: every,
+        init: init, t: t, tr: tr, state: state, fmt: fmt,
+        applyTheme: applyTheme, esc: esc, fmtBytes: fmtBytes, fmtAgo: fmtAgo, fmtDate: fmtDate, bar: bar,
+        apiPost: apiPost, apiResult: apiResult, showMsg: showMsg
+    };
+})();

+ 149 - 0
src/web/SystemAO/cluster/cluster.css

@@ -0,0 +1,149 @@
+/*
+    Cluster Settings / Cluster Info shared styles
+*/
+/* Scoped with :where() so it neither leaks into the System Settings page
+   this is injected into nor outranks the class rules below */
+:where(#cl-root, #cl-root *) { box-sizing: border-box; margin: 0; padding: 0; }
+
+#cl-root {
+    --cl-bg:        #f5f5f7;
+    --cl-card:      #ffffff;
+    --cl-card-head: #fafafa;
+    --cl-border:    rgba(0,0,0,0.08);
+    --cl-text:      #1d1d1f;
+    --cl-dim:       #6e6e73;
+    --cl-muted:     #98989d;
+    --cl-accent:    #0071e3;
+    --cl-success:   #34c759;
+    --cl-warn:      #ff9f0a;
+    --cl-danger:    #ff3b30;
+    --cl-hover:     rgba(0,0,0,0.04);
+    --cl-input:     #ffffff;
+    --cl-chip:      rgba(0,113,227,0.10);
+    --cl-shadow:    0 1px 4px rgba(0,0,0,0.07), 0 0 0 0.5px rgba(0,0,0,0.06);
+
+    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
+    font-size: 14px;
+    -webkit-font-smoothing: antialiased;
+    color: var(--cl-text);
+    background: var(--cl-bg);
+    padding: 20px;
+    min-height: 100vh;
+}
+#cl-root.dark {
+    --cl-bg:        #1f1f1f;
+    --cl-card:      #2c2c2e;
+    --cl-card-head: #232325;
+    --cl-border:    rgba(255,255,255,0.09);
+    --cl-text:      #f2f2f7;
+    --cl-dim:       #aeaeb2;
+    --cl-muted:     #636366;
+    --cl-accent:    #2997ff;
+    --cl-success:   #30d158;
+    --cl-warn:      #ffd60a;
+    --cl-danger:    #ff453a;
+    --cl-hover:     rgba(255,255,255,0.05);
+    --cl-input:     #1c1c1e;
+    --cl-chip:      rgba(41,151,255,0.18);
+    --cl-shadow:    0 1px 6px rgba(0,0,0,0.35), 0 0 0 0.5px rgba(255,255,255,0.06);
+}
+
+#cl-header { display: flex; align-items: center; gap: 12px; margin-bottom: 22px; }
+#cl-header img { width: 40px; height: 40px; border-radius: 9px; }
+#cl-header h1 { font-size: 20px; font-weight: 600; letter-spacing: -0.2px; }
+#cl-header p  { font-size: 13px; color: var(--cl-dim); margin-top: 2px; }
+
+.cl-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
+@media (max-width: 900px) { .cl-grid { grid-template-columns: 1fr; } }
+
+.cl-card {
+    background: var(--cl-card); border-radius: 12px; box-shadow: var(--cl-shadow);
+    overflow: hidden; margin-bottom: 16px;
+}
+.cl-card-head {
+    display: flex; align-items: center; justify-content: space-between;
+    padding: 12px 16px; background: var(--cl-card-head); border-bottom: 1px solid var(--cl-border);
+    font-weight: 600; font-size: 14px;
+}
+.cl-card-head .cl-sub { font-weight: 400; color: var(--cl-dim); font-size: 12px; margin-left: 8px; }
+.cl-card-body { padding: 16px; }
+
+.cl-field { margin-bottom: 12px; }
+.cl-field label { display: block; font-size: 12px; color: var(--cl-dim); margin-bottom: 4px; }
+.cl-field input[type=text], .cl-field textarea, .cl-field select {
+    width: 100%; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--cl-border);
+    background: var(--cl-input); color: var(--cl-text); font: inherit; font-size: 13px;
+}
+.cl-field textarea { min-height: 72px; resize: vertical; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; }
+.cl-hint { font-size: 11px; color: var(--cl-muted); margin-top: 4px; }
+.cl-row { display: flex; gap: 8px; align-items: center; }
+.cl-row > * { flex: 1; }
+.cl-row > .cl-fixed { flex: 0 0 auto; }
+.cl-check { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--cl-text); cursor: pointer; }
+
+.cl-btn {
+    display: inline-flex; align-items: center; gap: 6px; padding: 7px 14px; border-radius: 8px;
+    border: 1px solid var(--cl-border); background: var(--cl-card); color: var(--cl-text);
+    font: inherit; font-size: 13px; cursor: pointer; white-space: nowrap;
+}
+.cl-btn:hover { background: var(--cl-hover); }
+.cl-btn.primary { background: var(--cl-accent); border-color: transparent; color: #fff; }
+.cl-btn.primary:hover { filter: brightness(1.08); }
+.cl-btn.danger { color: var(--cl-danger); }
+.cl-btn.small { padding: 4px 9px; font-size: 12px; }
+.cl-btn:disabled { opacity: 0.5; cursor: default; }
+.cl-btn svg { width: 13px; height: 13px; }
+
+.cl-kv { display: grid; grid-template-columns: 130px 1fr; gap: 6px 12px; font-size: 13px; }
+.cl-kv dt { color: var(--cl-dim); }
+.cl-kv dd { word-break: break-all; }
+.cl-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; }
+
+.cl-chips { display: flex; flex-wrap: wrap; gap: 6px; }
+.cl-chip { background: var(--cl-chip); color: var(--cl-accent); border-radius: 999px; padding: 2px 9px; font-size: 11px; font-weight: 500; }
+
+.cl-badge { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px; font-weight: 600; letter-spacing: 0.2px; color: #fff; }
+.cl-badge.ONLINE      { background: var(--cl-success); }
+.cl-badge.DEGRADED    { background: var(--cl-warn); color: #1d1d1f; }
+.cl-badge.UNKNOWN     { background: var(--cl-muted); }
+.cl-badge.OFFLINE     { background: var(--cl-danger); }
+.cl-badge.MAINTENANCE { background: #5e5ce6; }
+.cl-badge.DRAINING    { background: #ff9f0a; color: #1d1d1f; }
+
+.cl-bar { height: 6px; border-radius: 3px; background: var(--cl-border); overflow: hidden; margin-top: 4px; }
+.cl-bar > div { height: 100%; background: var(--cl-accent); }
+.cl-bar.warn > div { background: var(--cl-warn); }
+.cl-bar.bad > div { background: var(--cl-danger); }
+
+table.cl-table { width: 100%; border-collapse: collapse; font-size: 13px; }
+table.cl-table th { text-align: left; font-weight: 600; color: var(--cl-dim); font-size: 12px; padding: 8px 10px; border-bottom: 1px solid var(--cl-border); }
+table.cl-table td { padding: 10px; border-bottom: 1px solid var(--cl-border); vertical-align: top; }
+table.cl-table tr:last-child td { border-bottom: none; }
+table.cl-table tr:hover td { background: var(--cl-hover); }
+.cl-node-name { font-weight: 600; }
+.cl-node-meta { color: var(--cl-dim); font-size: 12px; margin-top: 2px; }
+.cl-actions { display: flex; gap: 4px; flex-wrap: wrap; }
+
+.cl-empty { text-align: center; color: var(--cl-muted); padding: 24px 0; font-size: 13px; }
+.cl-msg { padding: 8px 12px; border-radius: 8px; font-size: 13px; margin-top: 10px; display: none; }
+.cl-msg.ok { display: block; background: rgba(52,199,89,0.14); color: var(--cl-success); }
+.cl-msg.err { display: block; background: rgba(255,59,48,0.12); color: var(--cl-danger); }
+.cl-sep { height: 1px; background: var(--cl-border); margin: 14px 0; }
+.cl-token-box {
+    background: var(--cl-input); border: 1px dashed var(--cl-border); border-radius: 8px; padding: 10px;
+    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; word-break: break-all; margin-top: 8px;
+}
+.cl-inline-list { list-style: none; }
+.cl-inline-list li { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid var(--cl-border); font-size: 12px; }
+.cl-inline-list li:last-child { border-bottom: none; }
+#cl-refresh { color: var(--cl-muted); font-size: 11px; }
+
+/* Shared by Cluster Settings and Cluster Info */
+#cl-header .cl-links { display: flex; gap: 6px; }
+#cl-header .cl-links a { font-size: 12px; color: var(--cl-accent); text-decoration: none; padding: 5px 10px; border-radius: 8px; border: 1px solid var(--cl-border); background: var(--cl-card); }
+#cl-header .cl-links a:hover { background: var(--cl-hover); }
+.cl-notice { background: var(--cl-card); border-radius: 12px; box-shadow: var(--cl-shadow); padding: 18px 20px; margin-bottom: 16px; font-size: 13px; color: var(--cl-dim); }
+.cl-notice b { color: var(--cl-text); }
+.cl-toggle { display: flex; align-items: flex-start; gap: 10px; }
+.cl-toggle input { margin-top: 3px; }
+.cl-toggle .cl-hint { margin-top: 2px; }

File diff ditekan karena terlalu besar
+ 197 - 594
src/web/SystemAO/cluster/cluster.html


+ 424 - 0
src/web/SystemAO/cluster/clusterinfo.html

@@ -0,0 +1,424 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta name="mobile-web-app-capable" content="yes">
+    <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
+    <meta charset="UTF-8">
+    <title>Cluster Info</title>
+    <script src="../../script/jquery.min.js"></script>
+    <script src="../../script/ao_module.js"></script>
+    <script src="../../script/applocale.js"></script>
+    <script src="../cluster/cluster.common.js"></script>
+    <link rel="stylesheet" href="../cluster/cluster.css">
+</head>
+<body>
+<div id="cl-root">
+    <div id="cl-header">
+        <img src="../cluster/img/small_icon.png" alt="">
+        <div style="flex:1">
+            <h1 locale="cluster/info/title">Cluster Info</h1>
+            <p locale="cluster/info/tagline">Health of this node, its peers, the namespace and the scheduler</p>
+        </div>
+        <span id="cl-refresh"></span>
+        <div class="cl-links">
+            <a href="../cluster/cluster.html" onclick="return CL.openPage('Cluster Settings', this.href)" locale="cluster/link/settings">Cluster Settings</a>
+            <a href="../cluster/jobs.html" onclick="return CL.openPage('Cluster Jobs', this.href)" locale="cluster/link/jobs">Cluster Jobs</a>
+        </div>
+    </div>
+
+    <div class="cl-notice" id="notice-standalone" style="display:none">
+        <b locale="cluster/info/standalone">This node is not part of a cluster.</b>
+        <span locale="cluster/info/standalonehint">Create or join one in Cluster Settings. Until then the cluster:/ drive stays hidden.</span>
+    </div>
+    <div class="cl-notice" id="notice-novolume" style="display:none">
+        <b locale="cluster/info/novolume">The cluster has no storage yet.</b>
+        <span locale="cluster/info/novolumehint">Add a volume in Cluster Settings to make the cluster:/ drive appear in File Manager.</span>
+    </div>
+
+    <div class="cl-grid">
+        <!-- This node -->
+        <div class="cl-card">
+            <div class="cl-card-head">
+                <span><span locale="cluster/thisnode">This node</span><span class="cl-sub cl-mono" id="cl-nodeid"></span></span>
+                <button class="cl-btn small" onclick="refreshCapabilities()" title="Re-detect capabilities">
+                    <svg viewBox="0 0 16 16" fill="none"><path d="M13 8a5 5 0 1 1-1.5-3.6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M13 2v3h-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
+                    <span locale="cluster/redetect">Re-detect</span>
+                </button>
+            </div>
+            <div class="cl-card-body">
+                <dl class="cl-kv">
+                    <dt locale="cluster/reach">Reachability</dt><dd id="v-reach">-</dd>
+                    <dt locale="cluster/version">Version</dt><dd id="v-version">-</dd>
+                    <dt locale="cluster/platform">Platform</dt><dd id="v-platform">-</dd>
+                    <dt locale="cluster/key">Node key</dt><dd class="cl-mono" id="v-key">-</dd>
+                    <dt locale="cluster/caps">Capabilities</dt><dd><div class="cl-chips" id="v-caps"></div></dd>
+                </dl>
+                <div class="cl-sep"></div>
+                <dl class="cl-kv" id="v-health"></dl>
+            </div>
+        </div>
+
+        <!-- Cluster summary -->
+        <div class="cl-card" id="card-summary" style="display:none">
+            <div class="cl-card-head"><span id="v-clustername">Cluster</span></div>
+            <div class="cl-card-body">
+                <dl class="cl-kv">
+                    <dt locale="cluster/clusterid">Cluster ID</dt><dd class="cl-mono" id="v-clusterid">-</dd>
+                    <dt locale="cluster/created">Created</dt><dd id="v-created">-</dd>
+                    <dt locale="cluster/members">Members</dt><dd id="v-membercount">-</dd>
+                </dl>
+                <div class="cl-sep"></div>
+                <dl class="cl-kv" id="meta-kv"></dl>
+            </div>
+        </div>
+    </div>
+
+    <!-- Nodes -->
+    <div class="cl-card" id="card-nodes" style="display:none">
+        <div class="cl-card-head"><span locale="cluster/nodes">Nodes</span></div>
+        <div class="cl-card-body" style="padding:0; overflow-x:auto">
+            <table class="cl-table">
+                <thead><tr>
+                    <th locale="cluster/node">Node</th>
+                    <th locale="cluster/state">State</th>
+                    <th locale="cluster/route">Route</th>
+                    <th locale="cluster/platform">Platform</th>
+                    <th locale="cluster/load">Load</th>
+                    <th locale="cluster/lastseen">Last seen</th>
+                    <th></th>
+                </tr></thead>
+                <tbody id="nodes-body"></tbody>
+            </table>
+        </div>
+        <div class="cl-card-body" style="padding-top:0"><div class="cl-msg" id="msg-nodes"></div></div>
+    </div>
+
+    <!-- Replication -->
+    <div class="cl-card" id="card-repl" style="display:none">
+        <div class="cl-card-head">
+            <span><span locale="cluster/repl/title">Replication</span><span class="cl-sub" id="repl-summary"></span></span>
+            <span>
+                <button class="cl-btn small" onclick="verifyCopies()"><span locale="cluster/repl/verify">Verify this node</span></button>
+                <button class="cl-btn small" onclick="planNow()"><span locale="cluster/repl/plan">Repair now</span></button>
+            </span>
+        </div>
+        <div class="cl-card-body">
+            <div class="cl-grid">
+                <dl class="cl-kv" id="repl-kv"></dl>
+                <div>
+                    <div class="cl-field">
+                        <label locale="cluster/repl/inspect">Inspect a file</label>
+                        <div class="cl-row">
+                            <input type="text" id="inspect-path" placeholder="cluster:/photos/a.jpg">
+                            <button class="cl-btn cl-fixed" onclick="inspectFile()"><span locale="cluster/repl/inspectbtn">Show copies</span></button>
+                        </div>
+                    </div>
+                    <ul class="cl-inline-list" id="inspect-list"></ul>
+                </div>
+            </div>
+            <div style="overflow-x:auto; margin-top:12px">
+                <table class="cl-table">
+                    <thead><tr><th locale="cluster/repl/task">Task</th><th locale="cluster/repl/route">From / to</th><th locale="cluster/state">State</th><th locale="cluster/when">When</th></tr></thead>
+                    <tbody id="repl-tasks"></tbody>
+                </table>
+            </div>
+            <div class="cl-msg" id="msg-repl"></div>
+        </div>
+    </div>
+
+    <!-- Scheduling -->
+    <div class="cl-card" id="card-sched" style="display:none">
+        <div class="cl-card-head">
+            <span><span locale="cluster/sched/title">Scheduling</span><span class="cl-sub" id="sched-summary"></span></span>
+        </div>
+        <div class="cl-card-body">
+            <div class="cl-hint" locale="cluster/sched/infohint">How each node scores right now with the weights set in Cluster Settings. Jobs, new files and extra copies go to the highest score.</div>
+            <div style="overflow-x:auto; margin-top:10px">
+                <table class="cl-table">
+                    <thead><tr>
+                        <th locale="cluster/node">Node</th><th locale="cluster/sched/cpu">CPU free</th><th locale="cluster/sched/mem">RAM free</th>
+                        <th locale="cluster/sched/diskfree">Disk free</th><th locale="cluster/sched/ping">Round trip</th><th locale="cluster/sched/score">Score</th>
+                    </tr></thead>
+                    <tbody id="sched-body"></tbody>
+                </table>
+            </div>
+            <div id="sched-matrix-wrap" style="display:none; margin-top:12px">
+                <div class="cl-hint" locale="cluster/sched/matrixhint">Round trip between each pair of nodes, in milliseconds. Copies of a file are spread over the slowest pairs, because distance usually means a different site.</div>
+                <div style="overflow-x:auto; margin-top:6px">
+                    <table class="cl-table" id="sched-matrix"></table>
+                </div>
+            </div>
+            <div class="cl-sep"></div>
+            <div class="cl-field">
+                <label locale="cluster/sched/explain">Why did a job land there?</label>
+                <div class="cl-row">
+                    <input type="text" id="sched-job" placeholder="job id">
+                    <button class="cl-btn cl-fixed" onclick="explainJob()"><span locale="cluster/sched/explainbtn">Explain</span></button>
+                </div>
+                <div class="cl-hint" locale="cluster/sched/explainhint">Copy a job id from the Cluster Jobs page to see the ranking it was placed with.</div>
+            </div>
+            <ul class="cl-inline-list" id="sched-explain"></ul>
+            <div class="cl-msg" id="msg-sched"></div>
+        </div>
+    </div>
+</div>
+
+<script>
+    var t = CL.t, fmt = CL.fmt, esc = CL.esc;
+    var API = '../../system/cluster/';
+    var clusterStatus = null;
+    CL.applyTheme('cl-root');
+
+    function post(path, data, cb) { CL.apiPost(API + path, data, cb); }
+    function fail(box, err, r) {
+        var e = err || CL.apiResult(r);
+        if (e) { CL.showMsg(box, e, false); return true; }
+        return false;
+    }
+    function nodeName(id) {
+        if (!clusterStatus) return id;
+        for (var i = 0; i < clusterStatus.nodes.length; i++) if (clusterStatus.nodes[i].id === id) return clusterStatus.nodes[i].name;
+        return String(id || '').substring(0, 8);
+    }
+    function badge(state, label) { return '<span class="cl-badge ' + esc(state) + '">' + esc(label || CL.state(state)) + '</span>'; }
+
+    /* This node */
+    function healthRows(h) {
+        if (!h || !h.timestamp) return '<dt>' + esc(t('cluster/load', 'Load')) + '</dt><dd style="color:var(--cl-muted)">' + esc(t('cluster/noreport', 'no report yet')) + '</dd>';
+        var ramPct = h.ramTotal > 0 ? h.ramUsed / h.ramTotal * 100 : 0;
+        var diskPct = h.diskTotal > 0 ? (h.diskTotal - h.diskFree) / h.diskTotal * 100 : 0;
+        return '<dt>CPU</dt><dd>' + h.cpuUsage.toFixed(0) + '%' + CL.bar(h.cpuUsage) + '</dd>' +
+            '<dt>RAM</dt><dd>' + (h.ramTotal > 0 ? CL.fmtBytes(h.ramUsed) + ' / ' + CL.fmtBytes(h.ramTotal) + CL.bar(ramPct) : '-') + '</dd>' +
+            '<dt>' + esc(t('cluster/disk', 'Disk')) + '</dt><dd>' + (h.diskTotal > 0 ? esc(fmt(t('cluster/diskfree', '{0} free of {1}'), CL.fmtBytes(h.diskFree), CL.fmtBytes(h.diskTotal))) + CL.bar(diskPct) : '-') + '</dd>' +
+            '<dt>' + esc(t('cluster/uptime', 'Uptime')) + '</dt><dd>' + esc(fmt(t('cluster/uptimevalue', '{0}h {1}m'), Math.floor(h.uptime / 3600), Math.floor((h.uptime % 3600) / 60))) + '</dd>';
+    }
+    function routeOf(n) {
+        if (n.local) return esc(t('cluster/thisnode', 'This node'));
+        if (n.tunnel) return esc(t('cluster/tunnelhere', 'Tunnel to this node'));
+        if (n.advertiseUrl) return '<a href="' + esc(n.advertiseUrl) + '" target="_blank" style="color:var(--cl-accent)">' + esc(n.advertiseUrl.replace(/^https?:\/\//, '')) + '</a>';
+        if (n.tunnelVia) return esc(fmt(t('cluster/tunnelvia', 'Tunnel via {0}'), nodeName(n.tunnelVia)));
+        return '<span style="color:var(--cl-muted)">' + esc(t('cluster/noroute', 'No route')) + '</span>';
+    }
+    function render() {
+        var s = clusterStatus, local = s.local;
+        document.getElementById('cl-nodeid').textContent = ' ' + local.nodeId;
+        var reach;
+        if (local.reachable) {
+            reach = badge('ONLINE', t('cluster/direct', 'DIRECT')) + ' ' + esc(local.config.advertiseUrl);
+        } else if (local.tunnelConnected) {
+            reach = badge('ONLINE', t('cluster/tunnel', 'TUNNEL')) + ' ' + esc(fmt(t('cluster/via', 'via {0}'), nodeName(local.tunnelHost)));
+        } else if (s.inCluster) {
+            reach = badge('OFFLINE', t('cluster/notunnel', 'NO TUNNEL')) + ' ' + esc(t('cluster/waitinghost', 'waiting for a reachable member'));
+        } else {
+            reach = badge('UNKNOWN', t('cluster/natonly', 'NO URL')) + ' ' + esc(t('cluster/willtunnel', 'will tunnel through a member once in a cluster'));
+        }
+        document.getElementById('v-reach').innerHTML = reach;
+        document.getElementById('v-version').textContent = local.version;
+        var c = local.capabilities || {};
+        document.getElementById('v-platform').textContent = fmt(t('cluster/platformline', '{0} / {1}, {2} cores'), c.os || '?', c.arch || '?', c.cpuCores || '?') + (c.totalRam > 0 ? ', ' + CL.fmtBytes(c.totalRam) + ' RAM' : '') + (c.hostname ? ' (' + c.hostname + ')' : '');
+        document.getElementById('v-key').textContent = (local.publicKey || '').substring(0, 20) + '...';
+        var chips = '';
+        Object.keys(c.features || {}).filter(function (k) { return c.features[k]; }).sort().forEach(function (f) { chips += '<span class="cl-chip">' + esc(f) + '</span>'; });
+        document.getElementById('v-caps').innerHTML = chips || '<span style="color:var(--cl-muted)">-</span>';
+        document.getElementById('v-health').innerHTML = healthRows(local.health);
+
+        document.getElementById('notice-standalone').style.display = s.inCluster ? 'none' : '';
+        ['card-summary', 'card-nodes', 'card-repl', 'card-sched'].forEach(function (id) {
+            document.getElementById(id).style.display = s.inCluster ? '' : 'none';
+        });
+        if (s.inCluster) {
+            document.getElementById('v-clustername').textContent = s.cluster.name;
+            document.getElementById('v-clusterid').textContent = s.cluster.id;
+            document.getElementById('v-created').textContent = CL.fmtDate(s.cluster.created);
+            document.getElementById('v-membercount').textContent = s.nodes.length;
+            renderNodes(s.nodes || []);
+        } else {
+            document.getElementById('notice-novolume').style.display = 'none';
+        }
+        document.getElementById('cl-refresh').textContent = fmt(t('cluster/updated', 'Updated {0}'), new Date().toLocaleTimeString());
+    }
+    function renderNodes(nodes) {
+        var html = '';
+        nodes.forEach(function (n) {
+            var c = n.capabilities || {}, h = n.health || {};
+            var load = h.timestamp
+                ? 'CPU ' + h.cpuUsage.toFixed(0) + '%' + (h.ramTotal > 0 ? ' ยท RAM ' + (h.ramUsed / h.ramTotal * 100).toFixed(0) + '%' : '') + (h.diskTotal > 0 ? ' ยท ' + esc(fmt(t('cluster/freeamount', '{0} free'), CL.fmtBytes(h.diskFree))) : '')
+                : '<span style="color:var(--cl-muted)">-</span>';
+            var feats = Object.keys(c.features || {}).filter(function (k) { return c.features[k] && ['ffmpeg', 'docker', 'cuda', 'gpu', 'git'].indexOf(k) >= 0; });
+            var probe = n.local ? '' : '<button class="cl-btn small" onclick="probeNode(\'' + esc(n.id) + '\', this)">' + esc(t('cluster/probe', 'Probe')) + '</button>';
+            html += '<tr>' +
+                '<td><div class="cl-node-name">' + esc(n.name) + (n.local ? ' <span class="cl-chip">' + esc(t('cluster/you', 'you')) + '</span>' : '') + '</div><div class="cl-node-meta cl-mono">' + esc(n.id) + '</div></td>' +
+                '<td>' + badge(n.state) + '</td>' +
+                '<td>' + routeOf(n) + '</td>' +
+                '<td>' + esc(c.os || '?') + '/' + esc(c.arch || '?') + '<div class="cl-node-meta">' + esc(fmt(t('cluster/coresvalue', '{0} cores'), c.cpuCores || '?')) + (c.totalRam > 0 ? ', ' + CL.fmtBytes(c.totalRam) : '') + (feats.length ? ' ยท ' + esc(feats.join(', ')) : '') + '</div></td>' +
+                '<td>' + load + '</td>' +
+                '<td>' + esc(n.local ? t('cluster/now', 'now') : CL.fmtAgo(n.lastSeen)) + '<div class="cl-node-meta">' + esc(n.version) + '</div></td>' +
+                '<td>' + probe + '</td></tr>';
+        });
+        document.getElementById('nodes-body').innerHTML = html || '<tr><td colspan="7" class="cl-empty">' + esc(t('cluster/nonodes', 'No nodes')) + '</td></tr>';
+    }
+    function refreshCapabilities() {
+        $.getJSON(API + 'capabilities?refresh=true', function () { loadStatus(); });
+    }
+    function probeNode(id, btn) {
+        btn.disabled = true;
+        $.getJSON(API + 'node/probe?id=' + encodeURIComponent(id), function (r) {
+            btn.disabled = false;
+            var route = String(r.route || '');
+            route = route.indexOf('relay via ') === 0
+                ? fmt(t('cluster/route/relay', 'relay via {0}'), nodeName(route.substring(10)))
+                : t('cluster/route/' + route, route);
+            if (r.ok) CL.showMsg('msg-nodes', fmt(t('cluster/probeok', '{0}: {1} ms ({2})'), nodeName(id), r.latencyMs, route), true);
+            else CL.showMsg('msg-nodes', fmt(t('cluster/probefail', '{0}: {1} ({2})'), nodeName(id), CL.tr(r.error || t('cluster/unreachable', 'unreachable')), route), false);
+        }).fail(function () { btn.disabled = false; });
+    }
+
+    /* Namespace */
+    function loadMeta() {
+        $.getJSON(API + 'meta/status', function (st) {
+            if (CL.apiResult(st)) return;
+            var leader = st.leader
+                ? esc(st.leaderName) + (st.isLeader ? ' <span class="cl-chip">' + esc(t('cluster/thisnode', 'This node')) + '</span>' : '') + ' <span class="cl-node-meta">' + esc(fmt(t('cluster/meta/term', 'term {0}'), st.term)) + '</span>'
+                : badge('UNKNOWN', t('cluster/meta/noleader', 'ELECTING'));
+            document.getElementById('meta-kv').innerHTML =
+                '<dt>' + esc(t('cluster/meta/leader', 'Leader')) + '</dt><dd>' + leader + '</dd>' +
+                '<dt>' + esc(t('cluster/meta/entries', 'Entries')) + '</dt><dd>' + esc(fmt(t('cluster/meta/entriesvalue', '{0} files, {1} folders, {2} volumes'), st.files, st.dirs, st.volumes)) + '</dd>' +
+                '<dt>' + esc(t('cluster/meta/log', 'Log')) + '</dt><dd>' + st.applied + ' / ' + st.lastSeq + (st.pending ? ' <span style="color:var(--cl-warn)">' + esc(fmt(t('cluster/meta/pending', '{0} pending'), st.pending)) + '</span>' : '') + '</dd>';
+            document.getElementById('notice-novolume').style.display = (clusterStatus && clusterStatus.inCluster && st.volumes === 0) ? '' : 'none';
+        });
+    }
+
+    /* Replication */
+    function loadRepl() {
+        $.getJSON(API + 'repl/status', function (st) {
+            if (CL.apiResult(st)) return;
+            var problems = st.underReplicated + st.noHealthyCopy + st.staleCopies;
+            document.getElementById('repl-summary').textContent = ' ' + (problems === 0 ? t('cluster/repl/healthy', 'all files at policy') : fmt(t('cluster/repl/issues', '{0} issues'), problems));
+            var planner = st.isLeader ? badge('ONLINE', t('cluster/thisnode', 'This node')) : (st.leader ? esc(st.leaderName) : badge('UNKNOWN', t('cluster/meta/noleader', 'ELECTING')));
+            if (st.lastPlan) planner += ' <span class="cl-node-meta">' + esc(fmt(t('cluster/repl/lastrun', 'last run {0}'), CL.fmtAgo(st.lastPlan))) + '</span>';
+            var inflight = fmt(t('cluster/repl/inflightvalue', '{0} running, {1} queued'), st.running, st.queued) + (st.evacuating ? ', ' + fmt(t('cluster/repl/evacuating', '{0} volume(s) evacuating'), st.evacuating) : '');
+            document.getElementById('repl-kv').innerHTML =
+                '<dt>' + esc(t('cluster/repl/planner', 'Planner')) + '</dt><dd>' + planner + '</dd>' +
+                '<dt>' + esc(t('cluster/repl/files', 'Files')) + '</dt><dd>' + st.files + '</dd>' +
+                '<dt>' + esc(t('cluster/repl/under', 'Under-replicated')) + '</dt><dd>' + (st.underReplicated ? '<span style="color:var(--cl-warn)">' + st.underReplicated + '</span>' : '0') + '</dd>' +
+                '<dt>' + esc(t('cluster/repl/nocopy', 'No healthy copy')) + '</dt><dd>' + (st.noHealthyCopy ? '<span style="color:var(--cl-danger)">' + st.noHealthyCopy + '</span>' : '0') + '</dd>' +
+                '<dt>' + esc(t('cluster/repl/stale', 'Stale copies')) + '</dt><dd>' + st.staleCopies + '</dd>' +
+                '<dt>' + esc(t('cluster/repl/over', 'Extra copies')) + '</dt><dd>' + st.overReplicated + '</dd>' +
+                '<dt>' + esc(t('cluster/repl/inflight', 'In flight')) + '</dt><dd>' + esc(inflight) + '</dd>';
+            var tasks = (st.tasks || []).concat((st.history || []).slice(0, 10));
+            var html = '';
+            tasks.forEach(function (tk) {
+                var cls = tk.state === 'done' ? 'ONLINE' : (tk.state === 'failed' ? 'OFFLINE' : (tk.state === 'running' ? 'DEGRADED' : 'UNKNOWN'));
+                html += '<tr><td><div class="cl-node-name">' + esc(tk.path) + '</div><div class="cl-node-meta">' + esc(t('cluster/repl/reason/' + tk.reason, tk.reason)) + ' ยท ' + CL.fmtBytes(tk.size) + (tk.error ? ' ยท <span style="color:var(--cl-danger)">' + esc(CL.tr(tk.error)) + '</span>' : '') + '</div></td>' +
+                    '<td>' + esc(fmt(t('cluster/repl/fromto', '{0} to {1}'), nodeName(tk.sourceNode), nodeName(tk.targetNode))) + '</td>' +
+                    '<td>' + badge(cls, CL.state(tk.state)) + (tk.attempts > 1 ? ' <span class="cl-node-meta">x' + tk.attempts + '</span>' : '') + '</td>' +
+                    '<td>' + esc(CL.fmtAgo(tk.updated || tk.created)) + '</td></tr>';
+            });
+            document.getElementById('repl-tasks').innerHTML = html || '<tr><td colspan="4" class="cl-empty">' + esc(t('cluster/repl/notasks', 'No replication tasks')) + '</td></tr>';
+        });
+    }
+    function planNow() {
+        post('repl/plan', {}, function (err, r) {
+            if (fail('msg-repl', err, r)) return;
+            CL.showMsg('msg-repl', fmt(t('cluster/repl/planned', 'Planner ran: {0} queued, {1} dropped, {2} marked stale'), r.queued, r.dropped, r.markedStale), true);
+            loadRepl();
+        });
+    }
+    function verifyCopies() {
+        post('repl/verify', { budget: 512 }, function (err, r) {
+            if (fail('msg-repl', err, r)) return;
+            CL.showMsg('msg-repl', fmt(t('cluster/repl/verified', 'Verified {0} copies ({1}), {2} stale'), r.checked, CL.fmtBytes(r.bytes), r.stale), r.stale === 0);
+            loadRepl();
+        });
+    }
+    function inspectFile() {
+        var p = document.getElementById('inspect-path').value.trim();
+        if (!p) return;
+        $.getJSON(API + 'meta/stat?path=' + encodeURIComponent(p), function (rec) {
+            if (fail('msg-repl', null, rec)) return;
+            var html = '<li><span>' + esc(rec.path) + '</span><span class="cl-node-meta">' + (rec.isDir ? esc(t('cluster/folder', 'folder')) : CL.fmtBytes(rec.size) + ' ยท ' + esc((rec.checksum || '').substring(0, 12))) + '</span></li>';
+            (rec.locations || []).forEach(function (l) {
+                var cls = l.state === 'verified' || l.state === 'committed' ? 'ONLINE' : (l.state === 'stale' || l.state === 'failed' ? 'OFFLINE' : 'DEGRADED');
+                html += '<li><span>' + esc(nodeName(l.nodeId)) + (l.volumeId === rec.primary ? ' <span class="cl-chip">' + esc(t('cluster/repl/primary', 'primary')) + '</span>' : '') + '</span><span>' + badge(cls, CL.state(l.state)) + ' <span class="cl-node-meta">' + esc(CL.fmtAgo(l.updated)) + '</span></span></li>';
+            });
+            if (!rec.isDir && (rec.locations || []).length === 0) html += '<li class="cl-empty">' + esc(t('cluster/repl/nolocations', 'No copies recorded')) + '</li>';
+            document.getElementById('inspect-list').innerHTML = html;
+        }).fail(function () { CL.showMsg('msg-repl', t('cluster/repl/notfound', 'Path not found in the namespace'), false); });
+    }
+
+    /* Scheduling */
+    function loadSched() {
+        $.getJSON(API + 'sched/status', function (st) {
+            if (CL.apiResult(st)) return;
+            var rows = '';
+            (st.nodes || []).forEach(function (n) {
+                var pct = Math.round(n.score * 100);
+                rows += '<tr><td><div class="cl-node-name">' + esc(n.name) + (n.local ? ' <span class="cl-chip">' + esc(t('cluster/thisnode', 'This node')) + '</span>' : '') + '</div>' +
+                    '<div class="cl-node-meta">' + badge(String(n.state).toUpperCase()) + ' ' + (n.features ? esc(fmt(t('cluster/sched/features', '{0} features'), n.features)) : '') + '</div></td>' +
+                    '<td>' + Math.round(n.cpuFree * 100) + '%</td>' +
+                    '<td>' + Math.round(n.memFree * 100) + '%</td>' +
+                    '<td>' + Math.round(n.diskFree * 100) + '%</td>' +
+                    '<td>' + (n.local ? esc(t('cluster/sched/here', 'local')) : (n.latencyMs < 0 ? '-' : Math.round(n.latencyMs) + ' ms')) + '</td>' +
+                    '<td>' + pct + '%' + CL.bar(pct) + '</td></tr>';
+            });
+            document.getElementById('sched-body').innerHTML = rows || '<tr><td colspan="6" class="cl-empty">' + esc(t('cluster/sched/nonodes', 'No nodes to score')) + '</td></tr>';
+            var top = (st.nodes || [])[0];
+            document.getElementById('sched-summary').textContent = top ? ' ' + fmt(t('cluster/sched/next', 'next job would run on {0}'), top.name) : '';
+            renderMatrix(st);
+        });
+    }
+    function renderMatrix(st) {
+        var wrap = document.getElementById('sched-matrix-wrap');
+        var nodes = st.nodes || [], matrix = st.matrix || {};
+        if (nodes.length < 2 || Object.keys(matrix).length === 0) { wrap.style.display = 'none'; return; }
+        var head = '<thead><tr><th>' + esc(t('cluster/sched/from', 'From / to')) + '</th>';
+        nodes.forEach(function (n) { head += '<th>' + esc(n.name) + '</th>'; });
+        var body = '</tr></thead><tbody>';
+        nodes.forEach(function (from) {
+            body += '<tr><td><div class="cl-node-name">' + esc(from.name) + '</div></td>';
+            nodes.forEach(function (to) {
+                var cell;
+                if (from.id === to.id) cell = '<span class="cl-node-meta">-</span>';
+                else {
+                    var v = (matrix[from.id] || {})[to.id];
+                    cell = (v === undefined) ? '<span class="cl-node-meta">' + esc(t('cluster/sched/unmeasured', 'not measured')) + '</span>' : (v < 10 ? v.toFixed(1) : Math.round(v)) + ' ms';
+                }
+                body += '<td>' + cell + '</td>';
+            });
+            body += '</tr>';
+        });
+        document.getElementById('sched-matrix').innerHTML = head + body + '</tbody>';
+        wrap.style.display = '';
+    }
+    function explainJob() {
+        var id = document.getElementById('sched-job').value.trim();
+        if (!id) return;
+        $.getJSON(API + 'sched/explain?job=' + encodeURIComponent(id), function (res) {
+            if (fail('msg-sched', null, res)) return;
+            var html = '<li><span>' + esc(res.job || id) + '</span><span class="cl-node-meta">' + esc(CL.state(res.status)) + (res.node ? ' ยท ' + esc(nodeName(res.node)) : '') + '</span></li>';
+            (res.ranking || []).forEach(function (r) {
+                var detail = r.eligible ? (r.factors || []).map(function (f) {
+                    return esc(t('cluster/sched/factor/' + f.name, f.name)) + ' ' + (f.points >= 0 ? '+' : '') + f.points.toFixed(2);
+                }).join(', ') : esc(CL.tr(r.reason));
+                html += '<li><span>' + esc(r.nodeName) + ' <span class="cl-node-meta">' + detail + '</span></span>' +
+                    '<span>' + (r.eligible ? (r.score * 100).toFixed(0) + '%' : badge('OFFLINE', t('cluster/sched/skipped', 'SKIPPED'))) + '</span></li>';
+            });
+            document.getElementById('sched-explain').innerHTML = html;
+        }).fail(function () { CL.showMsg('msg-sched', t('cluster/sched/nojob', 'No such job on this cluster'), false); });
+    }
+
+    function loadStatus() {
+        $.getJSON(API + 'status', function (data) {
+            if (CL.apiResult(data)) return;
+            clusterStatus = data;
+            render();
+            if (data.inCluster) { loadMeta(); loadRepl(); loadSched(); }
+        });
+    }
+    CL.init(function () {
+        loadStatus();
+        CL.every(loadStatus, 10000, 'cl-root');
+    });
+</script>
+</body>
+</html>

+ 53 - 72
src/web/SystemAO/cluster/jobs.html

@@ -8,8 +8,9 @@
     <script src="../../script/jquery.min.js"></script>
     <script src="../../script/ao_module.js"></script>
     <script src="../../script/applocale.js"></script>
+    <script src="../cluster/cluster.common.js"></script>
     <style>
-        * { box-sizing: border-box; margin: 0; padding: 0; }
+        :where(#cj-root, #cj-root *) { box-sizing: border-box; margin: 0; padding: 0; }
         #cj-root {
             --cj-bg:#f5f5f7; --cj-card:#ffffff; --cj-head:#fafafa; --cj-border:rgba(0,0,0,0.08);
             --cj-text:#1d1d1f; --cj-dim:#6e6e73; --cj-muted:#98989d; --cj-accent:#0071e3;
@@ -31,6 +32,9 @@
         #cj-header img { width:40px; height:40px; border-radius:9px; }
         #cj-header h1 { font-size:20px; font-weight:600; letter-spacing:-0.2px; }
         #cj-header p { font-size:13px; color:var(--cj-dim); margin-top:2px; }
+        #cj-header .cj-links { display:flex; gap:6px; }
+        #cj-header .cj-links a { font-size:12px; color:var(--cj-accent); text-decoration:none; padding:5px 10px; border-radius:8px; border:1px solid var(--cj-border); background:var(--cj-card); }
+        #cj-header .cj-links a:hover { background:var(--cj-hover); }
         .cj-card { background:var(--cj-card); border-radius:12px; box-shadow:var(--cj-shadow); overflow:hidden; margin-bottom:16px; }
         .cj-head { display:flex; align-items:center; justify-content:space-between; padding:12px 16px;
             background:var(--cj-head); border-bottom:1px solid var(--cj-border); font-weight:600; font-size:14px; }
@@ -89,59 +93,63 @@
 <body>
 <div id="cj-root">
     <div id="cj-header">
-        <img src="img/small_icon.png" alt="">
+        <img src="../cluster/img/small_icon.png" alt="">
         <div style="flex:1">
-            <h1 id="lbl-title">Cluster Jobs</h1>
-            <p id="lbl-tagline">Run a script on whichever node fits best</p>
+            <h1 id="lbl-title" locale="cluster/jobs/title">Cluster Jobs</h1>
+            <p id="lbl-tagline" locale="cluster/jobs/tagline">Run a script on whichever node fits best</p>
         </div>
         <span id="cj-refresh" style="color:var(--cj-muted); font-size:11px"></span>
+        <div class="cj-links">
+            <a href="../cluster/cluster.html" onclick="return CL.openPage('Cluster Settings', this.href)" locale="cluster/link/settings">Cluster Settings</a>
+            <a href="../cluster/clusterinfo.html" onclick="return CL.openPage('Cluster Info', this.href)" locale="cluster/link/info">Cluster Info</a>
+        </div>
     </div>
 
     <div class="cj-grid">
         <div class="cj-card">
-            <div class="cj-head"><span id="lbl-submit">Submit a job</span></div>
+            <div class="cj-head"><span id="lbl-submit" locale="cluster/jobs/submit">Submit a job</span></div>
             <div class="cj-body">
                 <div class="cj-field">
-                    <label id="lbl-name">Name</label>
+                    <label id="lbl-name" locale="cluster/jobs/name">Name</label>
                     <input type="text" id="job-name" placeholder="Nightly index">
                 </div>
                 <div class="cj-field">
-                    <label id="lbl-script">Script (a .agi file that defines run(job))</label>
+                    <label id="lbl-script" locale="cluster/jobs/scriptlabel">Script (a .agi file that defines run(job))</label>
                     <div class="cj-row">
                         <input type="text" id="job-script" placeholder="user:/jobs/index.agi">
-                        <button class="cj-btn cj-fixed" onclick="pickScript()"><span id="lbl-browse">Browse</span></button>
+                        <button class="cj-btn cj-fixed" onclick="pickScript()"><span id="lbl-browse" locale="cluster/jobs/browse">Browse</span></button>
                     </div>
                 </div>
                 <div class="cj-field">
-                    <label id="lbl-args">Arguments (JSON, optional)</label>
+                    <label id="lbl-args" locale="cluster/jobs/args">Arguments (JSON, optional)</label>
                     <textarea id="job-args" placeholder='{"album": "2026"}'></textarea>
                 </div>
                 <div class="cj-field">
-                    <label id="lbl-inputs">Input paths (comma separated, used to run near the data)</label>
+                    <label id="lbl-inputs" locale="cluster/jobs/inputs">Input paths (comma separated, used to run near the data)</label>
                     <input type="text" id="job-inputs" placeholder="cluster:/photos/2026">
                 </div>
                 <div class="cj-field">
-                    <label id="lbl-dataset">Map/reduce dataset (optional glob; turns this into a map/reduce job)</label>
+                    <label id="lbl-dataset" locale="cluster/jobs/datasetlabel">Map/reduce dataset (optional glob; turns this into a map/reduce job)</label>
                     <div class="cj-row">
                         <input type="text" id="job-dataset" placeholder="cluster:/photos/**/*.jpg">
                         <input type="text" id="job-partition" class="cj-fixed" style="width:120px" placeholder="50 per task">
                     </div>
-                    <div class="cj-hint" id="hint-dataset">With a dataset the script must define mapper(files, emit) and reducer(key, values); the files are processed on the nodes that hold them.</div>
+                    <div class="cj-hint" id="hint-dataset" locale="cluster/jobs/datasethint">With a dataset the script must define mapper(files, emit) and reducer(key, values); the files are processed on the nodes that hold them.</div>
                 </div>
                 <div class="cj-row">
                     <div class="cj-field" style="margin:0">
-                        <label id="lbl-features">Required features</label>
+                        <label id="lbl-features" locale="cluster/jobs/features">Required features</label>
                         <input type="text" id="job-features" placeholder="ffmpeg, cuda">
                     </div>
                     <div class="cj-field" style="margin:0; max-width:140px">
-                        <label id="lbl-timeout">Timeout (s)</label>
+                        <label id="lbl-timeout" locale="cluster/jobs/timeout">Timeout (s)</label>
                         <input type="text" id="job-timeout" value="3600">
                     </div>
                 </div>
-                <div class="cj-hint" id="hint-submit">The script runs with your permissions on the chosen node. cluster:/ paths work as usual.</div>
+                <div class="cj-hint" id="hint-submit" locale="cluster/jobs/submithint">The script runs with your permissions on the chosen node. cluster:/ paths work as usual.</div>
                 <div class="cj-row" style="margin-top:10px">
                     <div></div>
-                    <button class="cj-btn primary cj-fixed" onclick="submitJob()"><span id="lbl-run">Run job</span></button>
+                    <button class="cj-btn primary cj-fixed" onclick="submitJob()"><span id="lbl-run" locale="cluster/jobs/run">Run job</span></button>
                 </div>
                 <div class="cj-msg" id="msg-submit"></div>
             </div>
@@ -149,17 +157,17 @@
 
         <div class="cj-card">
             <div class="cj-head">
-                <span><span id="lbl-detail">Job detail</span><span class="cj-sub" id="detail-id"></span></span>
-                <button class="cj-btn small danger" id="btn-cancel" style="display:none" onclick="cancelSelected()"><span id="lbl-cancel">Cancel</span></button>
+                <span><span id="lbl-detail" locale="cluster/jobs/detail">Job detail</span><span class="cj-sub" id="detail-id"></span></span>
+                <button class="cj-btn small danger" id="btn-cancel" style="display:none" onclick="cancelSelected()"><span id="lbl-cancel" locale="cluster/cancel">Cancel</span></button>
             </div>
             <div class="cj-body">
-                <dl class="cj-kv" id="detail-kv"><dd class="cj-empty" style="grid-column:1/3" id="lbl-nodetail">Select a job to see its log and output</dd></dl>
+                <dl class="cj-kv" id="detail-kv"><dd class="cj-empty" style="grid-column:1/3" id="lbl-nodetail" locale="cluster/jobs/nodetail">Select a job to see its log and output</dd></dl>
                 <div id="detail-log-wrap" style="display:none">
-                    <div class="cj-hint" id="lbl-log">Log</div>
+                    <div class="cj-hint" id="lbl-log" locale="cluster/jobs/log">Log</div>
                     <pre class="cj-pre" id="detail-log"></pre>
                 </div>
                 <div id="detail-out-wrap" style="display:none">
-                    <div class="cj-hint" id="lbl-output">Output</div>
+                    <div class="cj-hint" id="lbl-output" locale="cluster/jobs/output">Output</div>
                     <pre class="cj-pre" id="detail-out"></pre>
                 </div>
             </div>
@@ -167,12 +175,12 @@
     </div>
 
     <div class="cj-card">
-        <div class="cj-head"><span><span id="lbl-jobs">Jobs</span><span class="cj-sub" id="jobs-summary"></span></span></div>
+        <div class="cj-head"><span><span id="lbl-jobs" locale="cluster/jobs/jobs">Jobs</span><span class="cj-sub" id="jobs-summary"></span></span></div>
         <div class="cj-body" style="padding:0; overflow-x:auto">
             <table class="cj-table">
                 <thead><tr>
-                    <th id="th-job">Job</th><th id="th-status">Status</th><th id="th-node">Node</th>
-                    <th id="th-progress">Progress</th><th id="th-when">When</th><th></th>
+                    <th id="th-job" locale="cluster/jobs/job">Job</th><th id="th-status" locale="cluster/jobs/status">Status</th><th id="th-node" locale="cluster/node">Node</th>
+                    <th id="th-progress" locale="cluster/jobs/progress">Progress</th><th id="th-when" locale="cluster/when">When</th><th></th>
                 </tr></thead>
                 <tbody id="jobs-body"></tbody>
             </table>
@@ -181,24 +189,7 @@
 </div>
 
 <script>
-    var cjLocale = (typeof NewAppLocale === 'function') ? NewAppLocale() : null;
-    function t(key, fallback) { return cjLocale ? cjLocale.getString(key, fallback) : fallback; }
-    function cjTheme(isDark) { document.getElementById('cj-root').classList.toggle('dark', isDark); }
-    (function() {
-        try {
-            if (typeof ao_module_getSystemThemeColor === 'function') {
-                ao_module_getSystemThemeColor(function(c) { cjTheme(c !== 'whiteTheme'); });
-                return;
-            }
-        } catch(e) {}
-        try {
-            var theme = null;
-            if (typeof preferredTheme !== 'undefined') theme = preferredTheme;
-            else if (parent && typeof parent.preferredTheme !== 'undefined') theme = parent.preferredTheme;
-            if (theme) cjTheme(theme === 'dark' || theme === 'darkTheme');
-        } catch(e) {}
-    })();
-    window.detailPageThemeCallback = function(isDark) { cjTheme(isDark); };
+    var t = CL.t, fmt = CL.fmt;
 
     var API = '../../system/cluster/jobs/';
     var selected = null;
@@ -208,34 +199,21 @@
         return String(s === undefined || s === null ? '' : s)
             .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
     }
-    function fmtAgo(unix) {
-        if (!unix) return '-';
-        var d = Math.max(0, Math.floor(Date.now()/1000 - unix));
-        if (d < 5) return t('cluster/justnow', 'just now');
-        if (d < 60) return d + 's ' + t('cluster/ago', 'ago');
-        if (d < 3600) return Math.floor(d/60) + 'm ' + t('cluster/ago', 'ago');
-        if (d < 86400) return Math.floor(d/3600) + 'h ' + t('cluster/ago', 'ago');
-        return Math.floor(d/86400) + 'd ' + t('cluster/ago', 'ago');
-    }
+    function fmtAgo(unix) { return unix ? CL.fmtAgo(unix) : '-'; }
     function duration(rec) {
         if (!rec.state.started) return '-';
         var end = rec.state.finished || Math.floor(Date.now()/1000);
         var s = Math.max(0, end - rec.state.started);
-        if (s < 60) return s + 's';
-        return Math.floor(s/60) + 'm ' + (s%60) + 's';
-    }
-    function showMsg(id, text, ok) {
-        var el = document.getElementById(id);
-        el.textContent = text;
-        el.className = 'cj-msg ' + (ok ? 'ok' : 'err');
-        clearTimeout(el._t);
-        el._t = setTimeout(function() { el.className = 'cj-msg'; }, ok ? 5000 : 9000);
+        if (s < 60) return fmt(t('cluster/jobs/secs', '{0}s'), s);
+        return fmt(t('cluster/jobs/minsecs', '{0}m {1}s'), Math.floor(s/60), s%60);
     }
+    //Errors from the server are translated by CL.showMsg
+    function showMsg(id, text, ok) { CL.showMsg(id, text, ok, 'cj-msg'); }
     function apiPost(path, data, cb) {
         $.ajax({ url: API + path, method: 'POST', data: data, dataType: 'json' })
             .done(function(r) { cb(null, r); })
             .fail(function(xhr) {
-                var msg = xhr.responseText || 'request failed';
+                var msg = xhr.responseText || t('cluster/requestfailed', 'Request failed');
                 try { var j = JSON.parse(xhr.responseText); if (j.error) msg = j.error; } catch(e) {}
                 cb(msg, null);
             });
@@ -301,19 +279,19 @@
         document.getElementById('btn-cancel').style.display = running ? '' : 'none';
         var kids = lastJobs.filter(function(j) { return j.spec.parentId === rec.spec.id; });
         var rows = '<dt>' + t('cluster/jobs/name', 'Name') + '</dt><dd>' + esc(rec.spec.name) +
-            (rec.spec.kind && rec.spec.kind !== 'run' ? ' <span class="cj-chip">' + esc(rec.spec.kind) + '</span>' : '') + '</dd>' +
-            '<dt>' + t('cluster/jobs/status', 'Status') + '</dt><dd><span class="cj-badge ' + esc(rec.state.status) + '">' + esc(rec.state.status).toUpperCase() + '</span>' +
-            (rec.state.error ? ' <span style="color:var(--cj-danger)">' + esc(rec.state.error) + '</span>' : '') + '</dd>' +
+            (rec.spec.kind && rec.spec.kind !== 'run' ? ' <span class="cj-chip">' + esc(t('cluster/jobs/kind/' + rec.spec.kind, rec.spec.kind)) + '</span>' : '') + '</dd>' +
+            '<dt>' + t('cluster/jobs/status', 'Status') + '</dt><dd><span class="cj-badge ' + esc(rec.state.status) + '">' + esc(CL.state(rec.state.status)) + '</span>' +
+            (rec.state.error ? ' <span style="color:var(--cj-danger)">' + esc(CL.tr(rec.state.error)) + '</span>' : '') + '</dd>' +
             '<dt>' + t('cluster/jobs/node', 'Node') + '</dt><dd>' + (rec.state.node ? esc(rec.state.node) : '-') + '</dd>' +
             '<dt>' + t('cluster/jobs/script', 'Script') + '</dt><dd class="cj-mono">' + esc(rec.spec.scriptName) + '</dd>' +
             '<dt>' + t('cluster/jobs/owner', 'Owner') + '</dt><dd>' + esc(rec.spec.owner) + '</dd>' +
-            '<dt>' + t('cluster/jobs/duration', 'Duration') + '</dt><dd>' + duration(rec) + ' <span class="cj-meta">' + t('cluster/jobs/attempts', 'attempt') + ' ' + rec.state.attempts + '</span></dd>';
+            '<dt>' + t('cluster/jobs/duration', 'Duration') + '</dt><dd>' + duration(rec) + ' <span class="cj-meta">' + esc(fmt(t('cluster/jobs/attemptn', 'attempt {0}'), rec.state.attempts)) + '</span></dd>';
         if (rec.spec.dataset) rows += '<dt>' + t('cluster/jobs/dataset', 'Dataset') + '</dt><dd class="cj-mono">' + esc(rec.spec.dataset) + '</dd>';
         if (kids.length) {
             var doneKids = kids.filter(function(k) { return k.state.status === 'succeeded'; }).length;
-            rows += '<dt>' + t('cluster/jobs/tasks', 'Tasks') + '</dt><dd>' + doneKids + ' / ' + kids.length + ' ' + t('cluster/jobs/complete', 'complete') + '</dd>';
+            rows += '<dt>' + t('cluster/jobs/tasks', 'Tasks') + '</dt><dd>' + esc(fmt(t('cluster/jobs/complete', '{0} / {1} complete'), doneKids, kids.length)) + '</dd>';
         }
-        if (rec.state.reason) rows += '<dt>' + t('cluster/jobs/waiting', 'Waiting') + '</dt><dd style="color:var(--cj-warn)">' + esc(rec.state.reason) + '</dd>';
+        if (rec.state.reason) rows += '<dt>' + t('cluster/jobs/waiting', 'Waiting') + '</dt><dd style="color:var(--cj-warn)">' + esc(CL.tr(rec.state.reason)) + '</dd>';
         kv.innerHTML = rows;
         var logs = rec.state.log || [];
         document.getElementById('detail-log-wrap').style.display = logs.length ? '' : 'none';
@@ -336,15 +314,15 @@
                 var pct = Math.round((rec.state.progress || 0) * 100);
                 html += '<tr class="' + (rec.spec.id === selected ? 'selected' : '') + '" onclick="selectJob(\'' + esc(rec.spec.id) + '\')">' +
                     '<td><div class="cj-name">' + esc(rec.spec.name || rec.spec.scriptName) + '</div><div class="cj-meta cj-mono">' + esc(rec.spec.id.substring(0, 8)) + ' ยท ' + esc(rec.spec.owner) + '</div></td>' +
-                    '<td><span class="cj-badge ' + esc(rec.state.status) + '">' + esc(rec.state.status).toUpperCase() + '</span></td>' +
+                    '<td><span class="cj-badge ' + esc(rec.state.status) + '">' + esc(CL.state(rec.state.status)) + '</span></td>' +
                     '<td>' + (rec.state.node ? esc(rec.state.node) : '<span style="color:var(--cj-muted)">-</span>') + '</td>' +
                     '<td>' + pct + '%<div class="cj-bar"><div style="width:' + pct + '%"></div></div></td>' +
                     '<td>' + fmtAgo(rec.state.finished || rec.state.started || rec.spec.created) + '<div class="cj-meta">' + duration(rec) + '</div></td>' +
                     '<td>' + (running ? '<button class="cj-btn small danger" onclick="event.stopPropagation(); cancelJob(\'' + esc(rec.spec.id) + '\')">' + t('cluster/jobs/cancel', 'Cancel') + '</button>' : '') + '</td></tr>';
             });
             document.getElementById('jobs-body').innerHTML = html || '<tr><td colspan="6" class="cj-empty">' + t('cluster/jobs/none', 'No jobs yet') + '</td></tr>';
-            document.getElementById('jobs-summary').textContent = ' ' + st.running + ' ' + t('cluster/jobs/running', 'running') + ', ' + st.queued + ' ' + t('cluster/jobs/queued2', 'queued') + ', ' + st.succeeded + ' ' + t('cluster/jobs/done', 'done') + (st.failed ? ', ' + st.failed + ' ' + t('cluster/jobs/failed', 'failed') : '');
-            document.getElementById('cj-refresh').textContent = (st.leaderName ? t('cluster/jobs/scheduler', 'Scheduler') + ': ' + st.leaderName + ' ยท ' : '') + st.localBusy + '/' + st.localSlots + ' ' + t('cluster/jobs/slots', 'slots busy here');
+            document.getElementById('jobs-summary').textContent = ' ' + fmt(t('cluster/jobs/summary', '{0} running, {1} queued, {2} done'), st.running, st.queued, st.succeeded) + (st.failed ? ', ' + fmt(t('cluster/jobs/failedn', '{0} failed'), st.failed) : '');
+            document.getElementById('cj-refresh').textContent = (st.leaderName ? fmt(t('cluster/jobs/scheduler', 'Scheduler: {0}'), st.leaderName) + ' ยท ' : '') + fmt(t('cluster/jobs/slots', '{0}/{1} slots busy here'), st.localBusy, st.localSlots);
             renderDetail();
         });
     }
@@ -357,8 +335,11 @@
         });
     }
 
-    loadJobs();
-    setInterval(loadJobs, 3000);
+    CL.applyTheme('cj-root');
+    CL.init(function() {
+        loadJobs();
+        CL.every(loadJobs, 3000, 'cj-root');
+    });
 </script>
 </body>
 </html>

+ 25 - 1
src/web/SystemAO/file_system/file_explorer.css

@@ -1825,8 +1825,32 @@ body.fmResizing *{
     */
     pointer-events: none;
 }
+/* A failed row uses the "Retry" text link in the status line instead of the button */
 #uploadTab .uploadTask.failed .uploadTaskAction{
-    color: var(--upload_progress_failed);
+    display: none;
+}
+#uploadTab .uploadTaskRetry{
+    display: none;
+    flex: 0 0 auto;
+    white-space: nowrap;
+    color: var(--upload_progressbar);
+    cursor: pointer;
+}
+#uploadTab .uploadTask.failed .uploadTaskRetry{
+    display: inline-block;
+    margin-left: -2px;
+}
+#uploadTab .uploadTask.failed .uploadTaskRetry::before{
+    content: "";
+    display: inline-block;
+    width: 1px;
+    height: 12px;
+    margin-right: 10px;
+    vertical-align: -2px;
+    background-color: var(--fs-line);
+}
+#uploadTab .uploadTaskRetry:hover{
+    text-decoration: underline;
 }
 #uploadTab .uploadTaskAction:hover{
     background-color: var(--fs-fill-hover);

+ 8 - 0
src/web/SystemAO/file_system/js/explorer/uploadui.js

@@ -67,6 +67,7 @@ function appendUploadFileItem(filename, filesize){
             <span class="uploadTaskSize"></span>
             <span class="uploadTaskDoneLink" onclick="onUploadTaskDoneLink('${newuuid}');"></span>
             <span class="uploadTaskStatus"></span>
+            <span class="uploadTaskRetry" onclick="onUploadTaskButton('${newuuid}');"></span>
         </div>
         <div class="uploadTaskActions">
             <div class="uploadTaskCancel" onclick="cancelUploadTask('${newuuid}');">${FSIcons.closeCircle}</div>
@@ -342,6 +343,13 @@ function renderUploadTask(taskUUID){
     }
     row.find(".uploadTaskStatus").text(statusText);
 
+    //A failed row offers a "Retry" text link after the status instead of a round button
+    let retryEl = row.find(".uploadTaskRetry");
+    let retryText = info.state == "failed" ? applocale.getString("upload/retry", "Retry") : "";
+    if (retryEl.text() != retryText){
+        retryEl.text(retryText);
+    }
+
     //The other half of that line: the download link of a finished download.
     //Written only when it changes, for the same reason the glyph below is.
     let linkEl = row.find(".uploadTaskDoneLink");

+ 3048 - 0
src/web/SystemAO/locale/cluster.json

@@ -0,0 +1,3048 @@
+{
+    "author": "tobychui",
+    "version": "1.0",
+    "keys": {
+        "en-us": {
+            "name": "English (US)",
+            "fontFamily": "Arial, Helvetica, sans-serif",
+            "strings": {
+                "cluster/ago/d": "{0}d ago",
+                "cluster/ago/h": "{0}h ago",
+                "cluster/ago/m": "{0}m ago",
+                "cluster/ago/s": "{0}s ago",
+                "cluster/auto": "Automatic (any reachable member)",
+                "cluster/cancel": "Cancel",
+                "cluster/caps": "Capabilities",
+                "cluster/cfg/insecure": "Accept self-signed certificates from other nodes (LAN only)",
+                "cluster/cfg/name": "Node name",
+                "cluster/cfg/test": "Test",
+                "cluster/cfg/tunnel": "Tunnel through",
+                "cluster/cfg/url": "Advertised URL (how other nodes reach this one)",
+                "cluster/cfg/urlhint": "Leave empty if this node has no public address. It will then keep a tunnel open to a reachable member, which also works through Cloudflare.",
+                "cluster/clusterid": "Cluster ID",
+                "cluster/confirmleave": "Leave the cluster? Files and jobs on other nodes will no longer be reachable from here.",
+                "cluster/confirmremove": "Remove node \"{0}\"?",
+                "cluster/copied": "Token copied to clipboard",
+                "cluster/copy": "Copy",
+                "cluster/coresvalue": "{0} cores",
+                "cluster/create": "Create a new cluster",
+                "cluster/createbtn": "Create",
+                "cluster/created": "Created",
+                "cluster/createhint": "This node becomes the first member. Generate join tokens here afterwards to add more nodes.",
+                "cluster/direct": "DIRECT",
+                "cluster/disk": "Disk",
+                "cluster/diskfree": "{0} free of {1}",
+                "cluster/drain": "Drain",
+                "cluster/entername": "Enter a cluster name",
+                "cluster/entertoken": "Paste a join token",
+                "cluster/enterurl": "Enter a URL to test",
+                "cluster/expiresat": "expires {0}",
+                "cluster/folder": "folder",
+                "cluster/freeamount": "{0} free",
+                "cluster/gentoken": "Generate join token",
+                "cluster/id/accounts": "Accounts",
+                "cluster/id/apply": "Apply",
+                "cluster/id/conflicts": "Local only",
+                "cluster/id/conflictswhy": "a local account with the same name is kept as is",
+                "cluster/id/disabled": "Disabled (each node checks its own accounts)",
+                "cluster/id/fallback": "origin unreachable, local accounts used",
+                "cluster/id/forward": "FORWARD",
+                "cluster/id/hint": "The identity origin verifies every login in the cluster. Accounts are copied to the other nodes so they still work when the origin is unreachable. Groups are matched by name, so create the same permission groups on every node.",
+                "cluster/id/lastlogin": "Last login check",
+                "cluster/id/lastsync": "Last sync",
+                "cluster/id/local": "Each node verifies logins against its own accounts",
+                "cluster/id/mirrors": "{0} of {1} local accounts mirror the origin",
+                "cluster/id/mode": "Mode",
+                "cluster/id/origin": "ORIGIN",
+                "cluster/id/originhere": "This node verifies logins for the whole cluster",
+                "cluster/id/published": "{0} published to members",
+                "cluster/id/replicated": "Replicated",
+                "cluster/id/skipped": "Skipped",
+                "cluster/id/skippedwhy": "none of their groups exist on this node",
+                "cluster/id/sync": "Sync now",
+                "cluster/id/synced": "Accounts synchronised",
+                "cluster/id/title": "Identity (single sign-on)",
+                "cluster/id/verifiedby": "Logins verified by {0}",
+                "cluster/info/novolume": "The cluster has no storage yet.",
+                "cluster/info/novolumehint": "Add a volume in Cluster Settings to make the cluster:/ drive appear in File Manager.",
+                "cluster/info/standalone": "This node is not part of a cluster.",
+                "cluster/info/standalonehint": "Create or join one in Cluster Settings. Until then the cluster:/ drive stays hidden.",
+                "cluster/info/tagline": "Health of this node, its peers, the namespace and the scheduler",
+                "cluster/info/title": "Cluster Info",
+                "cluster/invite": "Add a node",
+                "cluster/jobs/args": "Arguments (JSON, optional)",
+                "cluster/jobs/attemptn": "attempt {0}",
+                "cluster/jobs/browse": "Browse",
+                "cluster/jobs/cancel": "Cancel",
+                "cluster/jobs/complete": "{0} / {1} complete",
+                "cluster/jobs/dataset": "Dataset",
+                "cluster/jobs/datasethint": "With a dataset the script must define mapper(files, emit) and reducer(key, values); the files are processed on the nodes that hold them.",
+                "cluster/jobs/datasetlabel": "Map/reduce dataset (optional glob; turns this into a map/reduce job)",
+                "cluster/jobs/detail": "Job detail",
+                "cluster/jobs/duration": "Duration",
+                "cluster/jobs/failedn": "{0} failed",
+                "cluster/jobs/features": "Required features",
+                "cluster/jobs/inputs": "Input paths (comma separated, used to run near the data)",
+                "cluster/jobs/job": "Job",
+                "cluster/jobs/jobs": "Jobs",
+                "cluster/jobs/kind/map": "map",
+                "cluster/jobs/kind/mapreduce": "map/reduce",
+                "cluster/jobs/kind/reduce": "reduce",
+                "cluster/jobs/log": "Log",
+                "cluster/jobs/minsecs": "{0}m {1}s",
+                "cluster/jobs/name": "Name",
+                "cluster/jobs/needscript": "A script path is required",
+                "cluster/jobs/node": "Node",
+                "cluster/jobs/nodetail": "Select a job to see its log and output",
+                "cluster/jobs/none": "No jobs yet",
+                "cluster/jobs/output": "Output",
+                "cluster/jobs/owner": "Owner",
+                "cluster/jobs/progress": "Progress",
+                "cluster/jobs/queued": "Job queued",
+                "cluster/jobs/run": "Run job",
+                "cluster/jobs/scheduler": "Scheduler: {0}",
+                "cluster/jobs/script": "Script",
+                "cluster/jobs/scriptlabel": "Script (a .agi file that defines run(job))",
+                "cluster/jobs/secs": "{0}s",
+                "cluster/jobs/slots": "{0}/{1} slots busy here",
+                "cluster/jobs/status": "Status",
+                "cluster/jobs/submit": "Submit a job",
+                "cluster/jobs/submithint": "The script runs with your permissions on the chosen node. cluster:/ paths work as usual.",
+                "cluster/jobs/summary": "{0} running, {1} queued, {2} done",
+                "cluster/jobs/tagline": "Run a script on whichever node fits best",
+                "cluster/jobs/tasks": "Tasks",
+                "cluster/jobs/timeout": "Timeout (s)",
+                "cluster/jobs/title": "Cluster Jobs",
+                "cluster/jobs/typepath": "Type the script path, for example user:/jobs/index.agi",
+                "cluster/jobs/waiting": "Waiting",
+                "cluster/join": "Join an existing cluster",
+                "cluster/joinbtn": "Join",
+                "cluster/joinhint": "Paste a join token generated on a reachable member of the cluster.",
+                "cluster/joining": "Joining...",
+                "cluster/justnow": "just now",
+                "cluster/key": "Node key",
+                "cluster/lastseen": "Last seen",
+                "cluster/leave": "Leave cluster",
+                "cluster/link/info": "Cluster Info",
+                "cluster/link/jobs": "Cluster Jobs",
+                "cluster/link/settings": "Cluster Settings",
+                "cluster/load": "Load",
+                "cluster/maintenance": "Maintenance",
+                "cluster/members": "Members",
+                "cluster/membershint": "Maintenance takes a node out of scheduling. Draining also moves its copies to other nodes so it can be switched off safely.",
+                "cluster/meta/entries": "Entries",
+                "cluster/meta/entriesvalue": "{0} files, {1} folders, {2} volumes",
+                "cluster/meta/leader": "Leader",
+                "cluster/meta/log": "Log",
+                "cluster/meta/noleader": "ELECTING",
+                "cluster/meta/pending": "{0} pending",
+                "cluster/meta/term": "term {0}",
+                "cluster/natonly": "NO URL",
+                "cluster/never": "never",
+                "cluster/node": "Node",
+                "cluster/nodes": "Nodes",
+                "cluster/nonodes": "No nodes",
+                "cluster/noreport": "no report yet",
+                "cluster/noroute": "No route",
+                "cluster/notmember": "Not part of a cluster",
+                "cluster/notunnel": "NO TUNNEL",
+                "cluster/now": "now",
+                "cluster/platform": "Platform",
+                "cluster/platformline": "{0} / {1}, {2} cores",
+                "cluster/pol/1": "1 copy",
+                "cluster/pol/2": "2 copies",
+                "cluster/pol/3": "3 copies",
+                "cluster/pol/4": "4 copies",
+                "cluster/pol/copies": "{0} copies",
+                "cluster/pol/hint": "Folders without a policy keep one copy. Extra copies are created by the replication service.",
+                "cluster/pol/label": "Copies per top-level folder",
+                "cluster/pol/set": "Set",
+                "cluster/pol/title": "Replica policies",
+                "cluster/probe": "Probe",
+                "cluster/probefail": "{0}: {1} ({2})",
+                "cluster/probeok": "{0}: {1} ms ({2})",
+                "cluster/reach": "Reachability",
+                "cluster/reachableok": "Reachable, round trip {0} ms",
+                "cluster/redetect": "Re-detect",
+                "cluster/remove": "Remove",
+                "cluster/repl/evacuating": "{0} volume(s) evacuating",
+                "cluster/repl/files": "Files",
+                "cluster/repl/fromto": "{0} to {1}",
+                "cluster/repl/healthy": "all files at policy",
+                "cluster/repl/inflight": "In flight",
+                "cluster/repl/inflightvalue": "{0} running, {1} queued",
+                "cluster/repl/inspect": "Inspect a file",
+                "cluster/repl/inspectbtn": "Show copies",
+                "cluster/repl/issues": "{0} issues",
+                "cluster/repl/lastrun": "last run {0}",
+                "cluster/repl/nocopy": "No healthy copy",
+                "cluster/repl/nolocations": "No copies recorded",
+                "cluster/repl/notasks": "No replication tasks",
+                "cluster/repl/notfound": "Path not found in the namespace",
+                "cluster/repl/over": "Extra copies",
+                "cluster/repl/plan": "Repair now",
+                "cluster/repl/planned": "Planner ran: {0} queued, {1} dropped, {2} marked stale",
+                "cluster/repl/planner": "Planner",
+                "cluster/repl/primary": "primary",
+                "cluster/repl/reason/evacuate": "evacuation",
+                "cluster/repl/reason/repair": "repair",
+                "cluster/repl/reason/replica": "new copy",
+                "cluster/repl/route": "From / to",
+                "cluster/repl/stale": "Stale copies",
+                "cluster/repl/task": "Task",
+                "cluster/repl/title": "Replication",
+                "cluster/repl/under": "Under-replicated",
+                "cluster/repl/verified": "Verified {0} copies ({1}), {2} stale",
+                "cluster/repl/verify": "Verify this node",
+                "cluster/requestfailed": "Request failed",
+                "cluster/resume": "Resume",
+                "cluster/revoke": "Revoke",
+                "cluster/route": "Route",
+                "cluster/route/direct": "direct",
+                "cluster/route/none": "no route",
+                "cluster/route/relay": "relay via {0}",
+                "cluster/route/tunnel": "tunnel",
+                "cluster/save": "Save",
+                "cluster/saved": "Saved",
+                "cluster/sched/cpu": "CPU free",
+                "cluster/sched/cpu/hint": "Prefer an idle processor",
+                "cluster/sched/degraded": "Health",
+                "cluster/sched/degraded/hint": "Avoid a node that is struggling",
+                "cluster/sched/disk": "Free disk",
+                "cluster/sched/disk/hint": "Prefer a node with room to grow",
+                "cluster/sched/diskfree": "Disk free",
+                "cluster/sched/diversity": "Site diversity",
+                "cluster/sched/diversity/hint": "Put a second copy far from the first, so one site going down loses nothing",
+                "cluster/sched/explain": "Why did a job land there?",
+                "cluster/sched/explainbtn": "Explain",
+                "cluster/sched/explainhint": "Copy a job id from the Cluster Jobs page to see the ranking it was placed with.",
+                "cluster/sched/factor/cpu free": "cpu free",
+                "cluster/sched/factor/degraded": "degraded",
+                "cluster/sched/factor/disk free": "disk free",
+                "cluster/sched/factor/locality": "locality",
+                "cluster/sched/factor/memory free": "memory free",
+                "cluster/sched/factor/network distance": "network distance",
+                "cluster/sched/factor/preferred features": "preferred features",
+                "cluster/sched/factor/queue depth": "queue depth",
+                "cluster/sched/factor/site diversity": "site diversity",
+                "cluster/sched/features": "{0} features",
+                "cluster/sched/from": "From / to",
+                "cluster/sched/here": "local",
+                "cluster/sched/hint": "Jobs, new files and extra copies go to the node with the highest score. Raise a weight to make that quality matter more. The resulting scores are shown on Cluster Info.",
+                "cluster/sched/infohint": "How each node scores right now with the weights set in Cluster Settings. Jobs, new files and extra copies go to the highest score.",
+                "cluster/sched/latency": "Network distance",
+                "cluster/sched/latency/hint": "Avoid a node far from the data",
+                "cluster/sched/locality": "Data locality",
+                "cluster/sched/locality/hint": "Prefer a node that already holds the data",
+                "cluster/sched/matrixhint": "Round trip between each pair of nodes, in milliseconds. Copies of a file are spread over the slowest pairs, because distance usually means a different site.",
+                "cluster/sched/mem": "RAM free",
+                "cluster/sched/memory": "Free memory",
+                "cluster/sched/memory/hint": "Prefer a node with spare RAM",
+                "cluster/sched/next": "next job would run on {0}",
+                "cluster/sched/nojob": "No such job on this cluster",
+                "cluster/sched/nonodes": "No nodes to score",
+                "cluster/sched/ping": "Round trip",
+                "cluster/sched/preferred": "Wanted features",
+                "cluster/sched/preferred/hint": "Prefer a node with the optional features a job asks for",
+                "cluster/sched/queue": "Queue depth",
+                "cluster/sched/queue/hint": "Avoid a node that is already busy",
+                "cluster/sched/reset": "Restore defaults",
+                "cluster/sched/restored": "Default weights restored",
+                "cluster/sched/save": "Save weights",
+                "cluster/sched/saved": "Weights saved for the whole cluster",
+                "cluster/sched/score": "Score",
+                "cluster/sched/skipped": "SKIPPED",
+                "cluster/sched/title": "Scheduling",
+                "cluster/sched/unmeasured": "not measured",
+                "cluster/settings/tagline": "Set up this node and how the cluster stores files and runs work",
+                "cluster/settings/title": "Cluster Settings",
+                "cluster/state": "State",
+                "cluster/state/cancelled": "CANCELLED",
+                "cluster/state/committed": "COMMITTED",
+                "cluster/state/degraded": "DEGRADED",
+                "cluster/state/done": "DONE",
+                "cluster/state/draining": "DRAINING",
+                "cluster/state/failed": "FAILED",
+                "cluster/state/maintenance": "MAINTENANCE",
+                "cluster/state/offline": "OFFLINE",
+                "cluster/state/online": "ONLINE",
+                "cluster/state/pending": "PENDING",
+                "cluster/state/queued": "QUEUED",
+                "cluster/state/running": "RUNNING",
+                "cluster/state/scheduled": "SCHEDULED",
+                "cluster/state/stale": "STALE",
+                "cluster/state/succeeded": "SUCCEEDED",
+                "cluster/state/unknown": "UNKNOWN",
+                "cluster/state/verified": "VERIFIED",
+                "cluster/state/writing": "WRITING",
+                "cluster/storage": "Storage",
+                "cluster/testing": "Testing...",
+                "cluster/thisnode": "This node",
+                "cluster/tokenhint": "Tokens can only be generated on a node with an advertised URL. Paste the token into the other node's Cluster Settings.",
+                "cluster/tokenline": "expires {0} ยท {1} uses",
+                "cluster/ttl/1h": "1 hour",
+                "cluster/ttl/24h": "24 hours",
+                "cluster/ttl/7d": "7 days",
+                "cluster/tunnel": "TUNNEL",
+                "cluster/tunnelhere": "Tunnel to this node",
+                "cluster/tunnelvia": "Tunnel via {0}",
+                "cluster/unreachable": "unreachable",
+                "cluster/updated": "Updated {0}",
+                "cluster/uptime": "Uptime",
+                "cluster/uptimevalue": "{0}h {1}m",
+                "cluster/version": "Version",
+                "cluster/via": "via {0}",
+                "cluster/vol/add": "Contribute a folder from this node",
+                "cluster/vol/addbtn": "Add volume",
+                "cluster/vol/added": "Volume added",
+                "cluster/vol/autoro": "Make nearly full volumes read only automatically",
+                "cluster/vol/autorohint": "Applies to every node. A volume under 5% free stops taking new files and takes them again above 7%. Turn this off to let volumes fill up; volumes it had locked become writable again within a minute.",
+                "cluster/vol/autorooff": "Volumes may now fill up completely",
+                "cluster/vol/autoroon": "Nearly full volumes will be made read only",
+                "cluster/vol/confirmevac": "Move every file off \"{0}\"? The volume becomes read only and is retired once all copies exist elsewhere.",
+                "cluster/vol/confirmremove": "Stop contributing \"{0}\"? Files stay on disk but copies only held there become unavailable.",
+                "cluster/vol/evacstarted": "Evacuation started: {0} files to move",
+                "cluster/vol/evacuate": "Evacuate",
+                "cluster/vol/evacuating": "EVACUATING",
+                "cluster/vol/files": "Files",
+                "cluster/vol/full": "nearly full",
+                "cluster/vol/hint": "Files written to cluster:/ are stored as ordinary files inside contributed folders. Files already in the folder are adopted into the namespace on the next rescan. The cluster:/ drive appears in File Manager once the cluster has at least one volume.",
+                "cluster/vol/none": "No volumes yet. Add a folder from this node to start storing files in cluster:/",
+                "cluster/vol/readonly": "Read only",
+                "cluster/vol/rescan": "Rescan this node",
+                "cluster/vol/rescanned": "Rescan done: {0} adopted, {1} stale",
+                "cluster/vol/ro": "read only",
+                "cluster/vol/space": "Free / total",
+                "cluster/vol/summary": "{0} volumes, {1} free",
+                "cluster/vol/volume": "Volume",
+                "cluster/vol/writable": "Make writable",
+                "cluster/waitinghost": "waiting for a reachable member",
+                "cluster/when": "When",
+                "cluster/willtunnel": "will tunnel through a member once in a cluster",
+                "cluster/you": "you",
+                "msg/AGI gateway not ready": "AGI gateway not ready",
+                "msg/Invalid MAC address": "Invalid MAC address",
+                "msg/POST required": "POST required",
+                "msg/URL must start with http:// or https://": "URL must start with http:// or https://",
+                "msg/a job id is required": "a job id is required",
+                "msg/a map/reduce job needs a dataset pattern": "a map/reduce job needs a dataset pattern",
+                "msg/a map/reduce script must define mapper(files, emit) and reducer(key, values)": "a map/reduce script must define mapper(files, emit) and reducer(key, values)",
+                "msg/advertise URL must start with http:// or https://": "advertise URL must start with http:// or https://",
+                "msg/args must be valid JSON": "args must be valid JSON",
+                "msg/cannot remove the namespace root": "cannot remove the namespace root",
+                "msg/checksum mismatch": "checksum mismatch",
+                "msg/chunk checksum mismatch": "chunk checksum mismatch",
+                "msg/chunk exceeds file size": "chunk exceeds file size",
+                "msg/chunk index out of range": "chunk index out of range",
+                "msg/chunk size invalid": "chunk size invalid",
+                "msg/cluster jobs not available": "cluster jobs not available",
+                "msg/cluster name cannot be empty": "cluster name cannot be empty",
+                "msg/cluster name required": "cluster name required",
+                "msg/directory not empty": "directory not empty",
+                "msg/disk usage not supported on this platform": "disk usage not supported on this platform",
+                "msg/drive required": "drive required",
+                "msg/empty response": "empty response",
+                "msg/enabled must be true or false": "enabled must be true or false",
+                "msg/event bus not available": "event bus not available",
+                "msg/event data too large (64 KB max)": "event data too large (64 KB max)",
+                "msg/event type required": "event type required",
+                "msg/evict notice is not for this node": "evict notice is not for this node",
+                "msg/file changed since the copy was planned": "file changed since the copy was planned",
+                "msg/file no longer exists in the namespace": "file no longer exists in the namespace",
+                "msg/file not found on volume": "file not found on volume",
+                "msg/folder path required": "folder path required",
+                "msg/folder required": "folder required",
+                "msg/hook not found": "hook not found",
+                "msg/insufficient cpu cores": "insufficient cpu cores",
+                "msg/insufficient memory": "insufficient memory",
+                "msg/invalid append payload": "invalid append payload",
+                "msg/invalid batch": "invalid batch",
+                "msg/invalid begin request": "invalid begin request",
+                "msg/invalid entry": "invalid entry",
+                "msg/invalid heartbeat": "invalid heartbeat",
+                "msg/invalid job spec": "invalid job spec",
+                "msg/invalid join request": "invalid join request",
+                "msg/invalid lease": "invalid lease",
+                "msg/invalid node URL": "invalid node URL",
+                "msg/invalid node public key": "invalid node public key",
+                "msg/invalid node record": "invalid node record",
+                "msg/invalid range": "invalid range",
+                "msg/invalid rename": "invalid rename",
+                "msg/invalid report": "invalid report",
+                "msg/invalid request": "invalid request",
+                "msg/invalid sync payload": "invalid sync payload",
+                "msg/invalid task": "invalid task",
+                "msg/invalid username": "invalid username",
+                "msg/is a directory": "is a directory",
+                "msg/job cancelled": "job cancelled",
+                "msg/job id required": "job id required",
+                "msg/job is limited to other nodes": "job is limited to other nodes",
+                "msg/job not found": "job not found",
+                "msg/job owner is required": "job owner is required",
+                "msg/job script is empty": "job script is empty",
+                "msg/job timed out": "job timed out",
+                "msg/join response does not match the token's cluster": "join response does not match the token's cluster",
+                "msg/join token has an invalid node URL": "join token has an invalid node URL",
+                "msg/join token is corrupted": "join token is corrupted",
+                "msg/join token is for another cluster": "join token is for another cluster",
+                "msg/join token is incomplete": "join token is incomplete",
+                "msg/join token is invalid or expired": "join token is invalid or expired",
+                "msg/join token required": "join token required",
+                "msg/metadata store not available": "metadata store not available",
+                "msg/no cluster volume available; add one in System Settings > Cluster Settings > Storage": "no cluster volume available; add one in System Settings > Cluster Settings > Storage",
+                "msg/no healthy copy of this file is reachable right now": "no healthy copy of this file is reachable right now",
+                "msg/no node is available": "no node is available",
+                "msg/node already has enough copies in flight": "node already has enough copies in flight",
+                "msg/node id is required": "node id is required",
+                "msg/node id required": "node id required",
+                "msg/node is DEGRADED": "node is DEGRADED",
+                "msg/node is DRAINING": "node is DRAINING",
+                "msg/node is MAINTENANCE": "node is MAINTENANCE",
+                "msg/node is OFFLINE": "node is OFFLINE",
+                "msg/node is UNKNOWN": "node is UNKNOWN",
+                "msg/node is degraded": "node is degraded",
+                "msg/node is draining": "node is draining",
+                "msg/node is maintenance": "node is maintenance",
+                "msg/node is not available": "node is not available",
+                "msg/node is offline": "node is offline",
+                "msg/node is unknown": "node is unknown",
+                "msg/node lost while running the job": "node lost while running the job",
+                "msg/node name cannot be empty": "node name cannot be empty",
+                "msg/node not found": "node not found",
+                "msg/node refused this job earlier": "node refused this job earlier",
+                "msg/not a directory": "not a directory",
+                "msg/not a file": "not a file",
+                "msg/not an ArozOS join token": "not an ArozOS join token",
+                "msg/not in a cluster": "not in a cluster",
+                "msg/not logged in": "not logged in",
+                "msg/offset beyond end of file": "offset beyond end of file",
+                "msg/only the metadata leader assigns jobs": "only the metadata leader assigns jobs",
+                "msg/only the metadata leader assigns replication tasks": "only the metadata leader assigns replication tasks",
+                "msg/owner, script and at least one event type are required": "owner, script and at least one event type are required",
+                "msg/path already exists": "path already exists",
+                "msg/path escapes the volume": "path escapes the volume",
+                "msg/path not found in the cluster namespace": "path not found in the cluster namespace",
+                "msg/path required": "path required",
+                "msg/peer has no reachable endpoint": "peer has no reachable endpoint",
+                "msg/permission denied": "permission denied",
+                "msg/previous node stopped reporting": "previous node stopped reporting",
+                "msg/refusing to delete the volume root": "refusing to delete the volume root",
+                "msg/refusing to drop the last healthy copy": "refusing to drop the last healthy copy",
+                "msg/replicas must be between 0 and 16": "replicas must be between 0 and 16",
+                "msg/replicas must be between 1 and 16": "replicas must be between 1 and 16",
+                "msg/request is not signed": "request is not signed",
+                "msg/request signature invalid": "request signature invalid",
+                "msg/request targets another cluster": "request targets another cluster",
+                "msg/request timestamp outside the accepted window": "request timestamp outside the accepted window",
+                "msg/requested log range was compacted, take a snapshot": "requested log range was compacted, take a snapshot",
+                "msg/script path required": "script path required",
+                "msg/sender is not a member of this cluster": "sender is not a member of this cluster",
+                "msg/shutting down": "shutting down",
+                "msg/source copy is not readable": "source copy is not readable",
+                "msg/target node is not connected through a tunnel on this node": "target node is not connected through a tunnel on this node",
+                "msg/task is for another node": "task is for another node",
+                "msg/the URL answered but it is not an ArozOS cluster endpoint": "the URL answered but it is not an ArozOS cluster endpoint",
+                "msg/the job has no dataset pattern": "the job has no dataset pattern",
+                "msg/the job owner has no account on this node": "the job owner has no account on this node",
+                "msg/the mappers emitted more than 8 MB; narrow the dataset or emit fewer values": "the mappers emitted more than 8 MB; narrow the dataset or emit fewer values",
+                "msg/the user root itself cannot be a cluster volume; pick a sub folder such as /cluster": "the user root itself cannot be a cluster volume; pick a sub folder such as /cluster",
+                "msg/this folder is already a cluster volume": "this folder is already a cluster volume",
+                "msg/this job script defines no run(job) function": "this job script defines no run(job) function",
+                "msg/this node cannot execute jobs": "this node cannot execute jobs",
+                "msg/this node has no advertised URL; generate the join token on a node that other nodes can reach": "this node has no advertised URL; generate the join token on a node that other nodes can reach",
+                "msg/this node is already part of a cluster, leave it first": "this node is already part of a cluster, leave it first",
+                "msg/this node is not part of a cluster": "this node is not part of a cluster",
+                "msg/this node is not the identity origin of the cluster": "this node is not the identity origin of the cluster",
+                "msg/this node is not the metadata leader": "this node is not the metadata leader",
+                "msg/timed out waiting for the job to finish": "timed out waiting for the job to finish",
+                "msg/token id required": "token id required",
+                "msg/token not found": "token not found",
+                "msg/tunnel closed": "tunnel closed",
+                "msg/unknown upload session": "unknown upload session",
+                "msg/unreachable": "unreachable",
+                "msg/unsupported node state": "unsupported node state",
+                "msg/upload incomplete": "upload incomplete",
+                "msg/url required": "url required",
+                "msg/use leave to remove this node": "use leave to remove this node",
+                "msg/user assertion expired": "user assertion expired",
+                "msg/user assertion invalid": "user assertion invalid",
+                "msg/user not found on the identity origin": "user not found on the identity origin",
+                "msg/volume id required": "volume id required",
+                "msg/volume is not on this node": "volume is not on this node",
+                "msg/volume not found": "volume not found",
+                "msg/volume not found on this node": "volume not found on this node",
+                "msgp/architecture {0} not allowed": "architecture {0} not allowed",
+                "msgp/cancelled by {0}": "cancelled by {0}",
+                "msgp/could not assemble the reduce input: {0}": "could not assemble the reduce input: {0}",
+                "msgp/could not create a map task: {0}": "could not create a map task: {0}",
+                "msgp/could not create the reduce task: {0}": "could not create the reduce task: {0}",
+                "msgp/drive {0} is not a local drive on this node": "drive {0} is not a local drive on this node",
+                "msgp/hook script not found: {0}": "hook script not found: {0}",
+                "msgp/invalid join response from {0}": "invalid join response from {0}",
+                "msgp/invalid node key encoding: {0}": "invalid node key encoding: {0}",
+                "msgp/invalid public key encoding: {0}": "invalid public key encoding: {0}",
+                "msgp/job timed out after {0}s": "job timed out after {0}s",
+                "msgp/local drive {0} is not mounted": "local drive {0} is not mounted",
+                "msgp/missing feature {0}": "missing feature {0}",
+                "msgp/no cluster volume available; add one in System Settings > Cluster Settings > Storage ({0})": "no cluster volume available; add one in System Settings > Cluster Settings > Storage ({0})",
+                "msgp/no reachable copy of: {0}": "no reachable copy of: {0}",
+                "msgp/node {0} could not take the job: {1}": "node {0} could not take the job: {1}",
+                "msgp/os {0} not allowed": "os {0} not allowed",
+                "msgp/permission denied: {0}": "permission denied: {0}",
+                "msgp/remote node returned {0}: {1}": "remote node returned {0}: {1}",
+                "msgp/script not found: {0}": "script not found: {0}",
+                "msgp/this volume holds the only copy of {0} file(s); evacuate it first": "this volume holds the only copy of {0} file(s); evacuate it first",
+                "msgp/unable to reach {0}: {1}": "unable to reach {0}: {1}",
+                "msgp/unreachable: {0}": "unreachable: {0}",
+                "msgp/unsupported node URL scheme: {0}": "unsupported node URL scheme: {0}",
+                "msgp/weight {0} must be a number": "weight {0} must be a number",
+                "msgp/weight {0} must be between 0 and 1": "weight {0} must be between 0 and 1",
+                "msgp/{0} produced an unreadable result": "{0} produced an unreadable result"
+            },
+            "placeholder": {
+                "Name (optional)": "Name (optional)",
+                "job id": "job id",
+                "Nightly index": "Nightly index",
+                "50 per task": "50 per task"
+            },
+            "titles": {
+                "Re-detect capabilities": "Re-detect capabilities"
+            }
+        },
+        "zh-tw": {
+            "name": "็น้ซ”ไธญๆ–‡๏ผˆๅฐ็ฃ๏ผ‰",
+            "fontFamily": "\"Microsoft JhengHei\",\"SimHei\", \"Apple LiGothic Medium\", \"STHeiti\"",
+            "strings": {
+                "cluster/ago/d": "{0} ๅคฉๅ‰",
+                "cluster/ago/h": "{0} ๅฐๆ™‚ๅ‰",
+                "cluster/ago/m": "{0} ๅˆ†้˜ๅ‰",
+                "cluster/ago/s": "{0} ็ง’ๅ‰",
+                "cluster/auto": "่‡ชๅ‹•๏ผˆไปปไธ€ๅฏ้€ฃ็ทš็š„ๆˆๅ“ก๏ผ‰",
+                "cluster/cancel": "ๅ–ๆถˆ",
+                "cluster/caps": "ๅŠŸ่ƒฝ",
+                "cluster/cfg/insecure": "ๆŽฅๅ—ๅ…ถไป–็ฏ€้ปž็š„่‡ช็ฐฝๆ†‘่ญ‰๏ผˆๅƒ…้™ๅ€ๅŸŸ็ถฒ่ทฏ๏ผ‰",
+                "cluster/cfg/name": "็ฏ€้ปžๅ็จฑ",
+                "cluster/cfg/test": "ๆธฌ่ฉฆ",
+                "cluster/cfg/tunnel": "็ถ“็”ฑ้€š้“้€ฃ็ทš",
+                "cluster/cfg/url": "ๅฐๅค–็ถฒๅ€๏ผˆๅ…ถไป–็ฏ€้ปž้€ฃ็ทšๅˆฐๆญค็ฏ€้ปž็š„ๆ–นๅผ๏ผ‰",
+                "cluster/cfg/urlhint": "่‹ฅๆญค็ฏ€้ปžๆฒ’ๆœ‰ๅ…ฌ้–‹ไฝๅ€่ซ‹็•™็ฉบใ€‚ๅฎƒๆœƒๆ”น็‚บๅฐไธ€ๅ€‹ๅฏ้€ฃ็ทš็š„ๆˆๅ“กไฟๆŒ้€š้“้€ฃ็ทš๏ผŒ้€™ไนŸ่ƒฝ็ฉฟ่ถŠ Cloudflareใ€‚",
+                "cluster/clusterid": "ๅข้›† ID",
+                "cluster/confirmleave": "่ฆ้›ข้–‹ๅข้›†ๅ—Ž๏ผŸไน‹ๅพŒๅฐ‡็„กๆณ•ๅพž้€™่ฃกๅญ˜ๅ–ๅ…ถไป–็ฏ€้ปžไธŠ็š„ๆช”ๆกˆ่ˆ‡ๅทฅไฝœใ€‚",
+                "cluster/confirmremove": "่ฆ็งป้™ค็ฏ€้ปžใ€Œ{0}ใ€ๅ—Ž๏ผŸ",
+                "cluster/copied": "ๅทฒๅฐ‡ๆฌŠๆ–่ค‡่ฃฝๅˆฐๅ‰ช่ฒผ็ฐฟ",
+                "cluster/copy": "่ค‡่ฃฝ",
+                "cluster/coresvalue": "{0} ๆ ธๅฟƒ",
+                "cluster/create": "ๅปบ็ซ‹ๆ–ฐๅข้›†",
+                "cluster/createbtn": "ๅปบ็ซ‹",
+                "cluster/created": "ๅปบ็ซ‹ๆ™‚้–“",
+                "cluster/createhint": "ๆญค็ฏ€้ปžๆœƒๆˆ็‚บ็ฌฌไธ€ๅ€‹ๆˆๅ“กใ€‚ไน‹ๅพŒๅœจ้€™่ฃก็”ข็”ŸๅŠ ๅ…ฅๆฌŠๆ–ไปฅๅŠ ๅ…ฅๆ›ดๅคš็ฏ€้ปžใ€‚",
+                "cluster/direct": "็›ด้€ฃ",
+                "cluster/disk": "็ฃ็ขŸ",
+                "cluster/diskfree": "ๅ‰ฉ้ค˜ {0}๏ผŒๅ…ฑ {1}",
+                "cluster/drain": "ๆŽ’็ฉบ",
+                "cluster/entername": "่ซ‹่ผธๅ…ฅๅข้›†ๅ็จฑ",
+                "cluster/entertoken": "่ซ‹่ฒผไธŠๅŠ ๅ…ฅๆฌŠๆ–",
+                "cluster/enterurl": "่ซ‹่ผธๅ…ฅ่ฆๆธฌ่ฉฆ็š„็ถฒๅ€",
+                "cluster/expiresat": "ๆ–ผ {0} ๅˆฐๆœŸ",
+                "cluster/folder": "่ณ‡ๆ–™ๅคพ",
+                "cluster/freeamount": "ๅ‰ฉ้ค˜ {0}",
+                "cluster/gentoken": "็”ข็”ŸๅŠ ๅ…ฅๆฌŠๆ–",
+                "cluster/id/accounts": "ๅธณ่™Ÿ",
+                "cluster/id/apply": "ๅฅ—็”จ",
+                "cluster/id/conflicts": "ๅƒ…้™ๆœฌๆฉŸ",
+                "cluster/id/conflictswhy": "ๅŒๅ็š„ๆœฌๆฉŸๅธณ่™Ÿๆœƒ็ถญๆŒๅŽŸ็‹€",
+                "cluster/id/disabled": "ๅœ็”จ๏ผˆๆฏๅ€‹็ฏ€้ปžๅ„่‡ช้ฉ—่ญ‰่‡ชๅทฑ็š„ๅธณ่™Ÿ๏ผ‰",
+                "cluster/id/fallback": "็„กๆณ•้€ฃ็ทšๅˆฐ่บซๅˆ†ไพ†ๆบ๏ผŒๅทฒๆ”น็”จๆœฌๆฉŸๅธณ่™Ÿ",
+                "cluster/id/forward": "่ฝ‰้€",
+                "cluster/id/hint": "่บซๅˆ†ไพ†ๆบๆœƒ้ฉ—่ญ‰ๅข้›†ไธญ็š„ๆฏไธ€ๆฌก็™ปๅ…ฅใ€‚ๅธณ่™Ÿๆœƒ่ค‡่ฃฝๅˆฐๅ…ถไป–็ฏ€้ปž๏ผŒๅ› ๆญคๅœจ่บซๅˆ†ไพ†ๆบ็„กๆณ•้€ฃ็ทšๆ™‚ไปๅฏไฝฟ็”จใ€‚็พค็ต„ไปฅๅ็จฑๅฐๆ‡‰๏ผŒ่ซ‹ๅœจๆฏๅ€‹็ฏ€้ปžไธŠๅปบ็ซ‹็›ธๅŒ็š„ๆฌŠ้™็พค็ต„ใ€‚",
+                "cluster/id/lastlogin": "ไธŠๆฌก็™ปๅ…ฅๆชขๆŸฅ",
+                "cluster/id/lastsync": "ไธŠๆฌกๅŒๆญฅ",
+                "cluster/id/local": "ๆฏๅ€‹็ฏ€้ปžไปฅ่‡ชๅทฑ็š„ๅธณ่™Ÿ้ฉ—่ญ‰็™ปๅ…ฅ",
+                "cluster/id/mirrors": "{1} ๅ€‹ๆœฌๆฉŸๅธณ่™Ÿไธญๆœ‰ {0} ๅ€‹่ˆ‡่บซๅˆ†ไพ†ๆบๅŒๆญฅ",
+                "cluster/id/mode": "ๆจกๅผ",
+                "cluster/id/origin": "ไพ†ๆบ",
+                "cluster/id/originhere": "ๆญค็ฏ€้ปž่ฒ ่ฒฌ้ฉ—่ญ‰ๆ•ดๅ€‹ๅข้›†็š„็™ปๅ…ฅ",
+                "cluster/id/published": "ๅทฒ็™ผๅธƒ {0} ๅ€‹็ตฆๆˆๅ“ก",
+                "cluster/id/replicated": "ๅทฒ่ค‡่ฃฝ",
+                "cluster/id/skipped": "ๅทฒ็•ฅ้Ž",
+                "cluster/id/skippedwhy": "ๆญค็ฏ€้ปžไธŠๆฒ’ๆœ‰ไป–ๅ€‘ๆ‰€ๅฑฌ็š„ไปปไฝ•็พค็ต„",
+                "cluster/id/sync": "็ซ‹ๅณๅŒๆญฅ",
+                "cluster/id/synced": "ๅธณ่™ŸๅทฒๅŒๆญฅ",
+                "cluster/id/title": "่บซๅˆ†๏ผˆๅ–ฎไธ€็™ปๅ…ฅ๏ผ‰",
+                "cluster/id/verifiedby": "็™ปๅ…ฅ็”ฑ {0} ้ฉ—่ญ‰",
+                "cluster/info/novolume": "ๅข้›†ๅฐšๆœช่จญๅฎšๅ„ฒๅญ˜็ฉบ้–“ใ€‚",
+                "cluster/info/novolumehint": "ๅœจๅข้›†่จญๅฎšไธญๅŠ ๅ…ฅๅ„ฒๅญ˜ๅท๏ผŒcluster:/ ็ฃ็ขŸๆ‰ๆœƒๅ‡บ็พๅœจๆช”ๆกˆ็ฎก็†ๅ™จไธญใ€‚",
+                "cluster/info/standalone": "ๆญค็ฏ€้ปžๅฐšๆœชๅŠ ๅ…ฅๅข้›†ใ€‚",
+                "cluster/info/standalonehint": "่ซ‹ๅœจๅข้›†่จญๅฎšไธญๅปบ็ซ‹ๆˆ–ๅŠ ๅ…ฅๅข้›†ใ€‚ๅœจๆญคไน‹ๅ‰ cluster:/ ็ฃ็ขŸๆœƒไฟๆŒ้šฑ่—ใ€‚",
+                "cluster/info/tagline": "ๆญค็ฏ€้ปžใ€ๅ…ถไป–็ฏ€้ปžใ€ๅ‘ฝๅ็ฉบ้–“่ˆ‡ๆŽ’็จ‹ๅ™จ็š„็‹€ๆ…‹",
+                "cluster/info/title": "ๅข้›†่ณ‡่จŠ",
+                "cluster/invite": "ๅŠ ๅ…ฅ็ฏ€้ปž",
+                "cluster/jobs/args": "ๅƒๆ•ธ๏ผˆJSON๏ผŒ้ธๅกซ๏ผ‰",
+                "cluster/jobs/attemptn": "็ฌฌ {0} ๆฌกๅ˜—่ฉฆ",
+                "cluster/jobs/browse": "็€่ฆฝ",
+                "cluster/jobs/cancel": "ๅ–ๆถˆ",
+                "cluster/jobs/complete": "ๅทฒๅฎŒๆˆ {0} / {1}",
+                "cluster/jobs/dataset": "่ณ‡ๆ–™้›†",
+                "cluster/jobs/datasethint": "ไฝฟ็”จ่ณ‡ๆ–™้›†ๆ™‚๏ผŒ่…ณๆœฌๅฟ…้ ˆๅฎš็พฉ mapper(files, emit) ่ˆ‡ reducer(key, values)๏ผ›ๆช”ๆกˆๆœƒๅœจๆŒๆœ‰ๅฎƒๅ€‘็š„็ฏ€้ปžไธŠ่™•็†ใ€‚",
+                "cluster/jobs/datasetlabel": "Map/reduce ่ณ‡ๆ–™้›†๏ผˆ้ธๅกซ็š„่ฌ็”จๅญ—ๅ…ƒ๏ผ›ๆœƒๅฐ‡ๆญคๅทฅไฝœ่ฎŠๆˆ map/reduce ๅทฅไฝœ๏ผ‰",
+                "cluster/jobs/detail": "ๅทฅไฝœ่ฉณ็ดฐ่ณ‡ๆ–™",
+                "cluster/jobs/duration": "่€—ๆ™‚",
+                "cluster/jobs/failedn": "{0} ๅ€‹ๅคฑๆ•—",
+                "cluster/jobs/features": "ๅฟ…่ฆๅŠŸ่ƒฝ",
+                "cluster/jobs/inputs": "่ผธๅ…ฅ่ทฏๅพ‘๏ผˆไปฅ้€—่™Ÿๅˆ†้š”๏ผŒ็”จๆ–ผๅœจ่ณ‡ๆ–™้™„่ฟ‘ๅŸท่กŒ๏ผ‰",
+                "cluster/jobs/job": "ๅทฅไฝœ",
+                "cluster/jobs/jobs": "ๅทฅไฝœ",
+                "cluster/jobs/kind/map": "map ๅญๅทฅไฝœ",
+                "cluster/jobs/kind/mapreduce": "map/reduce",
+                "cluster/jobs/kind/reduce": "reduce ๅญๅทฅไฝœ",
+                "cluster/jobs/log": "็ด€้Œ„",
+                "cluster/jobs/minsecs": "{0} ๅˆ† {1} ็ง’",
+                "cluster/jobs/name": "ๅ็จฑ",
+                "cluster/jobs/needscript": "ๅฟ…้ ˆๆŒ‡ๅฎš่…ณๆœฌ่ทฏๅพ‘",
+                "cluster/jobs/node": "็ฏ€้ปž",
+                "cluster/jobs/nodetail": "้ธๆ“‡ไธ€ๅ€‹ๅทฅไฝœไปฅๆŸฅ็œ‹ๅ…ถ็ด€้Œ„่ˆ‡่ผธๅ‡บ",
+                "cluster/jobs/none": "ๅฐš็„กๅทฅไฝœ",
+                "cluster/jobs/output": "่ผธๅ‡บ",
+                "cluster/jobs/owner": "ๆ“ๆœ‰่€…",
+                "cluster/jobs/progress": "้€ฒๅบฆ",
+                "cluster/jobs/queued": "ๅทฅไฝœๅทฒๆŽ’ๅ…ฅไฝ‡ๅˆ—",
+                "cluster/jobs/run": "ๅŸท่กŒๅทฅไฝœ",
+                "cluster/jobs/scheduler": "ๆŽ’็จ‹ๅ™จ๏ผš{0}",
+                "cluster/jobs/script": "่…ณๆœฌ",
+                "cluster/jobs/scriptlabel": "่…ณๆœฌ๏ผˆๅฎš็พฉ run(job) ็š„ .agi ๆช”ๆกˆ๏ผ‰",
+                "cluster/jobs/secs": "{0} ็ง’",
+                "cluster/jobs/slots": "ๆญค็ฏ€้ปž {0}/{1} ๅ€‹ๅŸท่กŒๆงฝๅฟ™็ขŒไธญ",
+                "cluster/jobs/status": "็‹€ๆ…‹",
+                "cluster/jobs/submit": "ๆไบคๅทฅไฝœ",
+                "cluster/jobs/submithint": "่…ณๆœฌๆœƒไปฅไฝ ็š„ๆฌŠ้™ๅœจ้ธๅฎš็š„็ฏ€้ปžไธŠๅŸท่กŒใ€‚cluster:/ ่ทฏๅพ‘็…งๅธธๅฏ็”จใ€‚",
+                "cluster/jobs/summary": "{0} ๅ€‹ๅŸท่กŒไธญ๏ผŒ{1} ๅ€‹ๆŽ’้šŠไธญ๏ผŒ{2} ๅ€‹ๅทฒๅฎŒๆˆ",
+                "cluster/jobs/tagline": "ๅœจๆœ€้ฉๅˆ็š„็ฏ€้ปžไธŠๅŸท่กŒ่…ณๆœฌ",
+                "cluster/jobs/tasks": "ๅญๅทฅไฝœ",
+                "cluster/jobs/timeout": "้€พๆ™‚๏ผˆ็ง’๏ผ‰",
+                "cluster/jobs/title": "ๅข้›†ๅทฅไฝœ",
+                "cluster/jobs/typepath": "่ซ‹่ผธๅ…ฅ่…ณๆœฌ่ทฏๅพ‘๏ผŒไพ‹ๅฆ‚ user:/jobs/index.agi",
+                "cluster/jobs/waiting": "็ญ‰ๅพ…ๅŽŸๅ› ",
+                "cluster/join": "ๅŠ ๅ…ฅ็พๆœ‰ๅข้›†",
+                "cluster/joinbtn": "ๅŠ ๅ…ฅ",
+                "cluster/joinhint": "่ฒผไธŠๅœจๅข้›†ไธญๅฏ้€ฃ็ทšๆˆๅ“กไธŠ็”ข็”Ÿ็š„ๅŠ ๅ…ฅๆฌŠๆ–ใ€‚",
+                "cluster/joining": "ๆญฃๅœจๅŠ ๅ…ฅ...",
+                "cluster/justnow": "ๅ‰›ๅ‰›",
+                "cluster/key": "็ฏ€้ปž้‡‘้‘ฐ",
+                "cluster/lastseen": "ๆœ€ๅพŒ้€ฃ็ทš",
+                "cluster/leave": "้›ข้–‹ๅข้›†",
+                "cluster/link/info": "ๅข้›†่ณ‡่จŠ",
+                "cluster/link/jobs": "ๅข้›†ๅทฅไฝœ",
+                "cluster/link/settings": "ๅข้›†่จญๅฎš",
+                "cluster/load": "่ฒ ่ผ‰",
+                "cluster/maintenance": "็ถญ่ญท",
+                "cluster/members": "ๆˆๅ“ก",
+                "cluster/membershint": "็ถญ่ญทๆจกๅผๆœƒ่ฎ“็ฏ€้ปž้€€ๅ‡บๆŽ’็จ‹ใ€‚ๆŽ’็ฉบ้‚„ๆœƒๆŠŠๅฎƒ็š„ๅ‰ฏๆœฌ็งปๅˆฐๅ…ถไป–็ฏ€้ปž๏ผŒไน‹ๅพŒๅณๅฏๅฎ‰ๅ…จ้—œๆฉŸใ€‚",
+                "cluster/meta/entries": "้ …็›ฎ",
+                "cluster/meta/entriesvalue": "{0} ๅ€‹ๆช”ๆกˆใ€{1} ๅ€‹่ณ‡ๆ–™ๅคพใ€{2} ๅ€‹ๅ„ฒๅญ˜ๅท",
+                "cluster/meta/leader": "ไธปๅฐŽ็ฏ€้ปž",
+                "cluster/meta/log": "็ด€้Œ„",
+                "cluster/meta/noleader": "้ธ่ˆ‰ไธญ",
+                "cluster/meta/pending": "{0} ็ญ†ๅพ…่™•็†",
+                "cluster/meta/term": "ไปปๆœŸ {0}",
+                "cluster/natonly": "็„ก็ถฒๅ€",
+                "cluster/never": "ๅพžๆœช",
+                "cluster/node": "็ฏ€้ปž",
+                "cluster/nodes": "็ฏ€้ปž",
+                "cluster/nonodes": "ๆฒ’ๆœ‰็ฏ€้ปž",
+                "cluster/noreport": "ๅฐš็„กๅ›žๅ ฑ",
+                "cluster/noroute": "็„ก่ทฏๅพ‘",
+                "cluster/notmember": "ๅฐšๆœชๅŠ ๅ…ฅๅข้›†",
+                "cluster/notunnel": "็„ก้€š้“",
+                "cluster/now": "็พๅœจ",
+                "cluster/platform": "ๅนณๅฐ",
+                "cluster/platformline": "{0} / {1}๏ผŒ{2} ๆ ธๅฟƒ",
+                "cluster/pol/1": "1 ไปฝๅ‰ฏๆœฌ",
+                "cluster/pol/2": "2 ไปฝๅ‰ฏๆœฌ",
+                "cluster/pol/3": "3 ไปฝๅ‰ฏๆœฌ",
+                "cluster/pol/4": "4 ไปฝๅ‰ฏๆœฌ",
+                "cluster/pol/copies": "{0} ไปฝๅ‰ฏๆœฌ",
+                "cluster/pol/hint": "ๆฒ’ๆœ‰่จญๅฎšๅŽŸๅ‰‡็š„่ณ‡ๆ–™ๅคพๅชไฟ็•™ไธ€ไปฝๅ‰ฏๆœฌใ€‚้กๅค–็š„ๅ‰ฏๆœฌ็”ฑ่ค‡่ฃฝๆœๅ‹™ๅปบ็ซ‹ใ€‚",
+                "cluster/pol/label": "ๆฏๅ€‹ๆœ€ไธŠๅฑค่ณ‡ๆ–™ๅคพ็š„ๅ‰ฏๆœฌๆ•ธ",
+                "cluster/pol/set": "่จญๅฎš",
+                "cluster/pol/title": "ๅ‰ฏๆœฌๅŽŸๅ‰‡",
+                "cluster/probe": "ๆŽขๆธฌ",
+                "cluster/probefail": "{0}๏ผš{1}๏ผˆ{2}๏ผ‰",
+                "cluster/probeok": "{0}๏ผš{1} ๆฏซ็ง’๏ผˆ{2}๏ผ‰",
+                "cluster/reach": "้€ฃ็ทšๆ–นๅผ",
+                "cluster/reachableok": "ๅฏ้€ฃ็ทš๏ผŒๅพ€่ฟ” {0} ๆฏซ็ง’",
+                "cluster/redetect": "้‡ๆ–ฐๅตๆธฌ",
+                "cluster/remove": "็งป้™ค",
+                "cluster/repl/evacuating": "{0} ๅ€‹ๅ„ฒๅญ˜ๅทๆ’ค้›ขไธญ",
+                "cluster/repl/files": "ๆช”ๆกˆ",
+                "cluster/repl/fromto": "{0} ๅˆฐ {1}",
+                "cluster/repl/healthy": "ๆ‰€ๆœ‰ๆช”ๆกˆ็š†็ฌฆๅˆๅŽŸๅ‰‡",
+                "cluster/repl/inflight": "้€ฒ่กŒไธญ",
+                "cluster/repl/inflightvalue": "{0} ๅ€‹ๅŸท่กŒไธญ๏ผŒ{1} ๅ€‹ๆŽ’้šŠไธญ",
+                "cluster/repl/inspect": "ๆชขๆŸฅๆช”ๆกˆ",
+                "cluster/repl/inspectbtn": "้กฏ็คบๅ‰ฏๆœฌ",
+                "cluster/repl/issues": "{0} ๅ€‹ๅ•้กŒ",
+                "cluster/repl/lastrun": "ไธŠๆฌกๅŸท่กŒ {0}",
+                "cluster/repl/nocopy": "็„กๅฅๅบทๅ‰ฏๆœฌ",
+                "cluster/repl/nolocations": "ๆฒ’ๆœ‰ๅ‰ฏๆœฌ็ด€้Œ„",
+                "cluster/repl/notasks": "ๆฒ’ๆœ‰่ค‡่ฃฝๅทฅไฝœ",
+                "cluster/repl/notfound": "ๅœจๅ‘ฝๅ็ฉบ้–“ไธญๆ‰พไธๅˆฐๆญค่ทฏๅพ‘",
+                "cluster/repl/over": "ๅคš้ค˜ๅ‰ฏๆœฌ",
+                "cluster/repl/plan": "็ซ‹ๅณไฟฎๅพฉ",
+                "cluster/repl/planned": "่ฆๅŠƒๅ™จๅทฒๅŸท่กŒ๏ผš{0} ๅ€‹ๆŽ’ๅ…ฅ๏ผŒ{1} ๅ€‹ๅˆช้™ค๏ผŒ{2} ๅ€‹ๆจ™่จ˜็‚บ้ŽๆœŸ",
+                "cluster/repl/planner": "่ฆๅŠƒๅ™จ",
+                "cluster/repl/primary": "ไธป่ฆ",
+                "cluster/repl/reason/evacuate": "ๆ’ค้›ข",
+                "cluster/repl/reason/repair": "ไฟฎๅพฉ",
+                "cluster/repl/reason/replica": "ๆ–ฐๅขžๅ‰ฏๆœฌ",
+                "cluster/repl/route": "ไพ†ๆบ / ็›ฎ็š„ๅœฐ",
+                "cluster/repl/stale": "้ŽๆœŸๅ‰ฏๆœฌ",
+                "cluster/repl/task": "ๅทฅไฝœ",
+                "cluster/repl/title": "ๅ‰ฏๆœฌ่ค‡่ฃฝ",
+                "cluster/repl/under": "ๅ‰ฏๆœฌไธ่ถณ",
+                "cluster/repl/verified": "ๅทฒ้ฉ—่ญ‰ {0} ไปฝๅ‰ฏๆœฌ๏ผˆ{1}๏ผ‰๏ผŒ{2} ไปฝ้ŽๆœŸ",
+                "cluster/repl/verify": "้ฉ—่ญ‰ๆญค็ฏ€้ปž",
+                "cluster/requestfailed": "่ฆๆฑ‚ๅคฑๆ•—",
+                "cluster/resume": "ๆขๅพฉ",
+                "cluster/revoke": "ๆ’ค้Šท",
+                "cluster/route": "่ทฏๅพ‘",
+                "cluster/route/direct": "็›ด้€ฃ",
+                "cluster/route/none": "็„ก่ทฏๅพ‘",
+                "cluster/route/relay": "็ถ“็”ฑ {0} ่ฝ‰้€",
+                "cluster/route/tunnel": "้€š้“",
+                "cluster/save": "ๅ„ฒๅญ˜",
+                "cluster/saved": "ๅทฒๅ„ฒๅญ˜",
+                "cluster/sched/cpu": "CPU ้–’็ฝฎ",
+                "cluster/sched/cpu/hint": "ๅ„ชๅ…ˆ้ธๆ“‡้–’็ฝฎ็š„่™•็†ๅ™จ",
+                "cluster/sched/degraded": "ๅฅๅบท็‹€ๆ…‹",
+                "cluster/sched/degraded/hint": "้ฟ้–‹ๆ•ˆ่ƒฝไธไฝณ็š„็ฏ€้ปž",
+                "cluster/sched/disk": "ๅฏ็”จ็ฃ็ขŸ",
+                "cluster/sched/disk/hint": "ๅ„ชๅ…ˆ้ธๆ“‡ๆœ‰ๆˆ้•ท็ฉบ้–“็š„็ฏ€้ปž",
+                "cluster/sched/diskfree": "็ฃ็ขŸๅ‰ฉ้ค˜",
+                "cluster/sched/diversity": "็ซ™้ปžๅˆ†ๆ•ฃ",
+                "cluster/sched/diversity/hint": "ๆŠŠ็ฌฌไบŒไปฝๅ‰ฏๆœฌๆ”พๅœจ้›ข็ฌฌไธ€ไปฝ่ผƒ้ ็š„ๅœฐๆ–น๏ผŒ้€™ๆจฃๆŸๅ€‹็ซ™้ปžๅœๆ“บๆ™‚ไธๆœƒ้บๅคฑ่ณ‡ๆ–™",
+                "cluster/sched/explain": "ๅทฅไฝœ็‚บไฝ•่ขซๅˆ†้…ๅˆฐ้‚ฃ่ฃก๏ผŸ",
+                "cluster/sched/explainbtn": "่ชชๆ˜Ž",
+                "cluster/sched/explainhint": "ๅพžๅข้›†ๅทฅไฝœ้ ้ข่ค‡่ฃฝๅทฅไฝœ ID๏ผŒๅณๅฏๆŸฅ็œ‹ๅˆ†้…ๆ™‚็š„ๆŽ’ๅใ€‚",
+                "cluster/sched/factor/cpu free": "CPU ้–’็ฝฎ",
+                "cluster/sched/factor/degraded": "ๆ•ˆ่ƒฝ้™ไฝŽ",
+                "cluster/sched/factor/disk free": "็ฃ็ขŸๅ‰ฉ้ค˜",
+                "cluster/sched/factor/locality": "่ณ‡ๆ–™ไฝ็ฝฎ",
+                "cluster/sched/factor/memory free": "่จ˜ๆ†ถ้ซ”ๅ‰ฉ้ค˜",
+                "cluster/sched/factor/network distance": "็ถฒ่ทฏ่ท้›ข",
+                "cluster/sched/factor/preferred features": "ๅๅฅฝๅŠŸ่ƒฝ",
+                "cluster/sched/factor/queue depth": "ไฝ‡ๅˆ—้•ทๅบฆ",
+                "cluster/sched/factor/site diversity": "็ซ™้ปžๅˆ†ๆ•ฃ",
+                "cluster/sched/features": "{0} ้ …ๅŠŸ่ƒฝ",
+                "cluster/sched/from": "ไพ†ๆบ / ็›ฎ็š„ๅœฐ",
+                "cluster/sched/here": "ๆœฌๆฉŸ",
+                "cluster/sched/hint": "ๅทฅไฝœใ€ๆ–ฐๆช”ๆกˆ่ˆ‡้กๅค–ๅ‰ฏๆœฌๆœƒไบค็ตฆๅˆ†ๆ•ธๆœ€้ซ˜็š„็ฏ€้ปžใ€‚่ชฟ้ซ˜ๆฌŠ้‡ๅฏ่ฎ“่ฉฒ้ …็‰น่ณชๆ›ด้‡่ฆใ€‚ๅฏฆ้š›ๅˆ†ๆ•ธ้กฏ็คบๅœจๅข้›†่ณ‡่จŠ้ ้ขใ€‚",
+                "cluster/sched/infohint": "ไพๅข้›†่จญๅฎšไธญ็š„ๆฌŠ้‡๏ผŒๅ„็ฏ€้ปž็›ฎๅ‰็š„ๅˆ†ๆ•ธใ€‚ๅทฅไฝœใ€ๆ–ฐๆช”ๆกˆ่ˆ‡้กๅค–ๅ‰ฏๆœฌๆœƒไบค็ตฆๅˆ†ๆ•ธๆœ€้ซ˜่€…ใ€‚",
+                "cluster/sched/latency": "็ถฒ่ทฏ่ท้›ข",
+                "cluster/sched/latency/hint": "้ฟ้–‹่ท้›ข่ณ‡ๆ–™ๅคช้ ็š„็ฏ€้ปž",
+                "cluster/sched/locality": "่ณ‡ๆ–™ไฝ็ฝฎ",
+                "cluster/sched/locality/hint": "ๅ„ชๅ…ˆ้ธๆ“‡ๅทฒๆŒๆœ‰่ณ‡ๆ–™็š„็ฏ€้ปž",
+                "cluster/sched/matrixhint": "ๆฏๅฐ็ฏ€้ปžไน‹้–“็š„ๅพ€่ฟ”ๆ™‚้–“๏ผˆๆฏซ็ง’๏ผ‰ใ€‚ๆช”ๆกˆ็š„ๅ‰ฏๆœฌๆœƒๅˆ†ๆ•ฃๅˆฐ่ท้›ขๆœ€้ ็š„็ฏ€้ปžไธŠ๏ผŒๅ› ็‚บ่ท้›ข้€šๅธธไปฃ่กจไธๅŒ็š„็ซ™้ปžใ€‚",
+                "cluster/sched/mem": "่จ˜ๆ†ถ้ซ”ๅ‰ฉ้ค˜",
+                "cluster/sched/memory": "ๅฏ็”จ่จ˜ๆ†ถ้ซ”",
+                "cluster/sched/memory/hint": "ๅ„ชๅ…ˆ้ธๆ“‡ๆœ‰ๅคš้ค˜่จ˜ๆ†ถ้ซ”็š„็ฏ€้ปž",
+                "cluster/sched/next": "ไธ‹ไธ€ๅ€‹ๅทฅไฝœๅฐ‡ๅœจ {0} ๅŸท่กŒ",
+                "cluster/sched/nojob": "ๆญคๅข้›†ไธญๆฒ’ๆœ‰้€™ๅ€‹ๅทฅไฝœ",
+                "cluster/sched/nonodes": "ๆฒ’ๆœ‰ๅฏ่ฉ•ๅˆ†็š„็ฏ€้ปž",
+                "cluster/sched/ping": "ๅพ€่ฟ”ๆ™‚้–“",
+                "cluster/sched/preferred": "ๅๅฅฝๅŠŸ่ƒฝ",
+                "cluster/sched/preferred/hint": "ๅ„ชๅ…ˆ้ธๆ“‡ๅ…ทๅ‚™ๅทฅไฝœๆ‰€่ฆๆฑ‚ไน‹้ธ็”จๅŠŸ่ƒฝ็š„็ฏ€้ปž",
+                "cluster/sched/queue": "ไฝ‡ๅˆ—้•ทๅบฆ",
+                "cluster/sched/queue/hint": "้ฟ้–‹ๅทฒ็ถ“ๅฟ™็ขŒ็š„็ฏ€้ปž",
+                "cluster/sched/reset": "้‚„ๅŽŸ้ ่จญๅ€ผ",
+                "cluster/sched/restored": "ๅทฒ้‚„ๅŽŸ้ ่จญๆฌŠ้‡",
+                "cluster/sched/save": "ๅ„ฒๅญ˜ๆฌŠ้‡",
+                "cluster/sched/saved": "ๆฌŠ้‡ๅทฒๅฅ—็”จๅˆฐๆ•ดๅ€‹ๅข้›†",
+                "cluster/sched/score": "ๅˆ†ๆ•ธ",
+                "cluster/sched/skipped": "็•ฅ้Ž",
+                "cluster/sched/title": "ๆŽ’็จ‹",
+                "cluster/sched/unmeasured": "ๅฐšๆœชๆธฌ้‡",
+                "cluster/settings/tagline": "่จญๅฎšๆญค็ฏ€้ปž๏ผŒไปฅๅŠๅข้›†ๅฆ‚ไฝ•ๅ„ฒๅญ˜ๆช”ๆกˆ่ˆ‡ๅŸท่กŒๅทฅไฝœ",
+                "cluster/settings/title": "ๅข้›†่จญๅฎš",
+                "cluster/state": "็‹€ๆ…‹",
+                "cluster/state/cancelled": "ๅทฒๅ–ๆถˆ",
+                "cluster/state/committed": "ๅทฒๅฏซๅ…ฅ",
+                "cluster/state/degraded": "ๆ•ˆ่ƒฝ้™ไฝŽ",
+                "cluster/state/done": "ๅฎŒๆˆ",
+                "cluster/state/draining": "ๆŽ’็ฉบไธญ",
+                "cluster/state/failed": "ๅคฑๆ•—",
+                "cluster/state/maintenance": "็ถญ่ญทไธญ",
+                "cluster/state/offline": "้›ข็ทš",
+                "cluster/state/online": "็ทšไธŠ",
+                "cluster/state/pending": "ๅพ…่™•็†",
+                "cluster/state/queued": "ๆŽ’้šŠไธญ",
+                "cluster/state/running": "ๅŸท่กŒไธญ",
+                "cluster/state/scheduled": "ๅทฒๆŽ’็จ‹",
+                "cluster/state/stale": "้ŽๆœŸ",
+                "cluster/state/succeeded": "ๆˆๅŠŸ",
+                "cluster/state/unknown": "ๆœช็Ÿฅ",
+                "cluster/state/verified": "ๅทฒ้ฉ—่ญ‰",
+                "cluster/state/writing": "ๅฏซๅ…ฅไธญ",
+                "cluster/storage": "ๅ„ฒๅญ˜็ฉบ้–“",
+                "cluster/testing": "ๆธฌ่ฉฆไธญ...",
+                "cluster/thisnode": "ๆญค็ฏ€้ปž",
+                "cluster/tokenhint": "ๅชๆœ‰่จญๅฎšไบ†ๅฐๅค–็ถฒๅ€็š„็ฏ€้ปžๆ‰่ƒฝ็”ข็”ŸๆฌŠๆ–ใ€‚ๅฐ‡ๆฌŠๆ–่ฒผๅˆฐๅฆไธ€ๅ€‹็ฏ€้ปž็š„ๅข้›†่จญๅฎšไธญใ€‚",
+                "cluster/tokenline": "ๆ–ผ {0} ๅˆฐๆœŸ ยท ๅทฒไฝฟ็”จ {1} ๆฌก",
+                "cluster/ttl/1h": "1 ๅฐๆ™‚",
+                "cluster/ttl/24h": "24 ๅฐๆ™‚",
+                "cluster/ttl/7d": "7 ๅคฉ",
+                "cluster/tunnel": "้€š้“",
+                "cluster/tunnelhere": "้€š้“้€ฃๅˆฐๆญค็ฏ€้ปž",
+                "cluster/tunnelvia": "็ถ“็”ฑ {0} ็š„้€š้“",
+                "cluster/unreachable": "็„กๆณ•้€ฃ็ทš",
+                "cluster/updated": "ๆ›ดๆ–ฐๆ–ผ {0}",
+                "cluster/uptime": "้‹ไฝœๆ™‚้–“",
+                "cluster/uptimevalue": "{0} ๅฐๆ™‚ {1} ๅˆ†",
+                "cluster/version": "็‰ˆๆœฌ",
+                "cluster/via": "็ถ“็”ฑ {0}",
+                "cluster/vol/add": "ๆไพ›ๆญค็ฏ€้ปžไธŠ็š„่ณ‡ๆ–™ๅคพ",
+                "cluster/vol/addbtn": "ๅŠ ๅ…ฅๅ„ฒๅญ˜ๅท",
+                "cluster/vol/added": "ๅทฒๅŠ ๅ…ฅๅ„ฒๅญ˜ๅท",
+                "cluster/vol/autoro": "่‡ชๅ‹•ๅฐ‡ๅฟซๆปฟ็š„ๅ„ฒๅญ˜ๅท่จญ็‚บๅ”ฏ่ฎ€",
+                "cluster/vol/autorohint": "ๅฅ—็”จๅˆฐๆฏๅ€‹็ฏ€้ปžใ€‚ๅ‰ฉ้ค˜็ฉบ้–“ไฝŽๆ–ผ 5% ็š„ๅ„ฒๅญ˜ๅทๆœƒๅœๆญขๆŽฅๆ”ถๆ–ฐๆช”ๆกˆ๏ผŒ้ซ˜ๆ–ผ 7% ๆ™‚ๅ†ๆขๅพฉใ€‚้—œ้–‰ๆญค้ …ๅฏ่ฎ“ๅ„ฒๅญ˜ๅท็”จๅˆฐๆปฟ๏ผ›ๅ…ˆๅ‰ๅ› ๆญค่ขซ้Ž–ๅฎš็š„ๅ„ฒๅญ˜ๅทๆœƒๅœจไธ€ๅˆ†้˜ๅ…งๆขๅพฉๅฏๅฏซๅ…ฅใ€‚",
+                "cluster/vol/autorooff": "ๅ„ฒๅญ˜ๅท็พๅœจๅฏไปฅๅฎŒๅ…จ็”จๆปฟ",
+                "cluster/vol/autoroon": "ๅฟซๆปฟ็š„ๅ„ฒๅญ˜ๅทๅฐ‡ๆœƒ่จญ็‚บๅ”ฏ่ฎ€",
+                "cluster/vol/confirmevac": "่ฆๅฐ‡ใ€Œ{0}ใ€ไธŠ็š„ๆ‰€ๆœ‰ๆช”ๆกˆ็งป่ตฐๅ—Ž๏ผŸๆญคๅ„ฒๅญ˜ๅทๆœƒ่ฎŠๆˆๅ”ฏ่ฎ€๏ผŒไธฆๅœจๆ‰€ๆœ‰ๅ‰ฏๆœฌ้ƒฝๅญ˜ๅœจๆ–ผๅˆฅ่™•ๅพŒ้€€ๅฝนใ€‚",
+                "cluster/vol/confirmremove": "่ฆๅœๆญขๆไพ›ใ€Œ{0}ใ€ๅ—Ž๏ผŸๆช”ๆกˆๆœƒ็•™ๅœจ็ฃ็ขŸไธŠ๏ผŒไฝ†ๅชๅญ˜ๅœจ้‚ฃ่ฃก็š„ๅ‰ฏๆœฌๅฐ‡็„กๆณ•ไฝฟ็”จใ€‚",
+                "cluster/vol/evacstarted": "ๅทฒ้–‹ๅง‹ๆ’ค้›ข๏ผš{0} ๅ€‹ๆช”ๆกˆๅพ…็งปๅ‹•",
+                "cluster/vol/evacuate": "ๆ’ค้›ข",
+                "cluster/vol/evacuating": "ๆ’ค้›ขไธญ",
+                "cluster/vol/files": "ๆช”ๆกˆ",
+                "cluster/vol/full": "ๅฟซๆปฟ",
+                "cluster/vol/hint": "ๅฏซๅ…ฅ cluster:/ ็š„ๆช”ๆกˆๆœƒไปฅไธ€่ˆฌๆช”ๆกˆๅญ˜ๆ”พๅœจๆไพ›็š„่ณ‡ๆ–™ๅคพไธญใ€‚่ณ‡ๆ–™ๅคพไธญๅŽŸๆœ‰็š„ๆช”ๆกˆๆœƒๅœจไธ‹ๆฌก้‡ๆ–ฐๆŽƒๆๆ™‚็ดๅ…ฅๅ‘ฝๅ็ฉบ้–“ใ€‚ๅข้›†่‡ณๅฐ‘ๆœ‰ไธ€ๅ€‹ๅ„ฒๅญ˜ๅทๅพŒ๏ผŒcluster:/ ็ฃ็ขŸๆ‰ๆœƒๅ‡บ็พๅœจๆช”ๆกˆ็ฎก็†ๅ™จไธญใ€‚",
+                "cluster/vol/none": "ๅฐš็„กๅ„ฒๅญ˜ๅทใ€‚ๅŠ ๅ…ฅๆญค็ฏ€้ปžไธŠ็š„่ณ‡ๆ–™ๅคพๅณๅฏ้–‹ๅง‹ๅœจ cluster:/ ไธญๅญ˜ๆ”พๆช”ๆกˆ",
+                "cluster/vol/readonly": "่จญ็‚บๅ”ฏ่ฎ€",
+                "cluster/vol/rescan": "้‡ๆ–ฐๆŽƒๆๆญค็ฏ€้ปž",
+                "cluster/vol/rescanned": "้‡ๆ–ฐๆŽƒๆๅฎŒๆˆ๏ผš็ดๅ…ฅ {0} ๅ€‹๏ผŒ้ŽๆœŸ {1} ๅ€‹",
+                "cluster/vol/ro": "ๅ”ฏ่ฎ€",
+                "cluster/vol/space": "ๅ‰ฉ้ค˜ / ็ธฝๅฎน้‡",
+                "cluster/vol/summary": "{0} ๅ€‹ๅ„ฒๅญ˜ๅท๏ผŒๅ‰ฉ้ค˜ {1}",
+                "cluster/vol/volume": "ๅ„ฒๅญ˜ๅท",
+                "cluster/vol/writable": "่จญ็‚บๅฏๅฏซๅ…ฅ",
+                "cluster/waitinghost": "ๆญฃๅœจ็ญ‰ๅพ…ๅฏ้€ฃ็ทš็š„ๆˆๅ“ก",
+                "cluster/when": "ๆ™‚้–“",
+                "cluster/willtunnel": "ๅŠ ๅ…ฅๅข้›†ๅพŒๅฐ‡้€้Žๆˆๅ“กๅปบ็ซ‹้€š้“",
+                "cluster/you": "ไฝ ",
+                "msg/AGI gateway not ready": "AGI ้–˜้“ๅฐšๆœชๅฐฑ็ท’",
+                "msg/Invalid MAC address": "MAC ไฝๅ€็„กๆ•ˆ",
+                "msg/POST required": "ๅฟ…้ ˆไฝฟ็”จ POST",
+                "msg/URL must start with http:// or https://": "็ถฒๅ€ๅฟ…้ ˆไปฅ http:// ๆˆ– https:// ้–‹้ ญ",
+                "msg/a job id is required": "ๅฟ…้ ˆๆไพ›ๅทฅไฝœ ID",
+                "msg/a map/reduce job needs a dataset pattern": "map/reduce ๅทฅไฝœ้œ€่ฆ่ณ‡ๆ–™้›†ๆจฃๅผ",
+                "msg/a map/reduce script must define mapper(files, emit) and reducer(key, values)": "map/reduce ่…ณๆœฌๅฟ…้ ˆๅฎš็พฉ mapper(files, emit) ่ˆ‡ reducer(key, values)",
+                "msg/advertise URL must start with http:// or https://": "ๅฐๅค–็ถฒๅ€ๅฟ…้ ˆไปฅ http:// ๆˆ– https:// ้–‹้ ญ",
+                "msg/args must be valid JSON": "ๅƒๆ•ธๅฟ…้ ˆๆ˜ฏๆœ‰ๆ•ˆ็š„ JSON",
+                "msg/cannot remove the namespace root": "็„กๆณ•็งป้™คๅ‘ฝๅ็ฉบ้–“็š„ๆ น็›ฎ้Œ„",
+                "msg/checksum mismatch": "ๆชขๆŸฅ็ขผไธ็ฌฆ",
+                "msg/chunk checksum mismatch": "ๅ€ๅกŠๆชขๆŸฅ็ขผไธ็ฌฆ",
+                "msg/chunk exceeds file size": "ๅ€ๅกŠ่ถ…้Žๆช”ๆกˆๅคงๅฐ",
+                "msg/chunk index out of range": "ๅ€ๅกŠ็ดขๅผ•่ถ…ๅ‡บ็ฏ„ๅœ",
+                "msg/chunk size invalid": "ๅ€ๅกŠๅคงๅฐ็„กๆ•ˆ",
+                "msg/cluster jobs not available": "ๅข้›†ๅทฅไฝœ็„กๆณ•ไฝฟ็”จ",
+                "msg/cluster name cannot be empty": "ๅข้›†ๅ็จฑไธๅฏ็‚บ็ฉบ",
+                "msg/cluster name required": "ๅฟ…้ ˆ่ผธๅ…ฅๅข้›†ๅ็จฑ",
+                "msg/directory not empty": "่ณ‡ๆ–™ๅคพไธๆ˜ฏ็ฉบ็š„",
+                "msg/disk usage not supported on this platform": "ๆญคๅนณๅฐไธๆ”ฏๆด็ฃ็ขŸไฝฟ็”จ้‡ๆŸฅ่ฉข",
+                "msg/drive required": "ๅฟ…้ ˆ้ธๆ“‡็ฃ็ขŸ",
+                "msg/empty response": "ๅ›žๆ‡‰ๆ˜ฏ็ฉบ็š„",
+                "msg/enabled must be true or false": "enabled ๅฟ…้ ˆๆ˜ฏ true ๆˆ– false",
+                "msg/event bus not available": "ไบ‹ไปถๅŒฏๆตๆŽ’็„กๆณ•ไฝฟ็”จ",
+                "msg/event data too large (64 KB max)": "ไบ‹ไปถ่ณ‡ๆ–™ๅคชๅคง๏ผˆไธŠ้™ 64 KB๏ผ‰",
+                "msg/event type required": "ๅฟ…้ ˆๆไพ›ไบ‹ไปถ้กžๅž‹",
+                "msg/evict notice is not for this node": "็งป้™ค้€š็Ÿฅไธๆ˜ฏ็ตฆๆญค็ฏ€้ปž็š„",
+                "msg/file changed since the copy was planned": "ๆช”ๆกˆๅœจ่ฆๅŠƒ่ค‡่ฃฝๅพŒๅทฒ่ฎŠๆ›ด",
+                "msg/file no longer exists in the namespace": "ๆช”ๆกˆๅทฒไธๅญ˜ๅœจๆ–ผๅ‘ฝๅ็ฉบ้–“ไธญ",
+                "msg/file not found on volume": "ๅ„ฒๅญ˜ๅทไธŠๆ‰พไธๅˆฐๆช”ๆกˆ",
+                "msg/folder path required": "ๅฟ…้ ˆ่ผธๅ…ฅ่ณ‡ๆ–™ๅคพ่ทฏๅพ‘",
+                "msg/folder required": "ๅฟ…้ ˆ่ผธๅ…ฅ่ณ‡ๆ–™ๅคพ",
+                "msg/hook not found": "ๆ‰พไธๅˆฐๆŽ›้‰ค",
+                "msg/insufficient cpu cores": "CPU ๆ ธๅฟƒไธ่ถณ",
+                "msg/insufficient memory": "่จ˜ๆ†ถ้ซ”ไธ่ถณ",
+                "msg/invalid append payload": "็„กๆ•ˆ็š„้™„ๅŠ ่ณ‡ๆ–™",
+                "msg/invalid batch": "็„กๆ•ˆ็š„ๆ‰นๆฌก",
+                "msg/invalid begin request": "็„กๆ•ˆ็š„้–‹ๅง‹่ฆๆฑ‚",
+                "msg/invalid entry": "็„กๆ•ˆ็š„้ …็›ฎ",
+                "msg/invalid heartbeat": "็„กๆ•ˆ็š„ๅฟƒ่ทณ",
+                "msg/invalid job spec": "็„กๆ•ˆ็š„ๅทฅไฝœ่จญๅฎš",
+                "msg/invalid join request": "็„กๆ•ˆ็š„ๅŠ ๅ…ฅ่ฆๆฑ‚",
+                "msg/invalid lease": "็„กๆ•ˆ็š„็งŸ็ด„",
+                "msg/invalid node URL": "็ฏ€้ปž็ถฒๅ€็„กๆ•ˆ",
+                "msg/invalid node public key": "็ฏ€้ปžๅ…ฌ้–‹้‡‘้‘ฐ็„กๆ•ˆ",
+                "msg/invalid node record": "็„กๆ•ˆ็š„็ฏ€้ปž็ด€้Œ„",
+                "msg/invalid range": "็„กๆ•ˆ็š„็ฏ„ๅœ",
+                "msg/invalid rename": "็„กๆ•ˆ็š„้‡ๆ–ฐๅ‘ฝๅ",
+                "msg/invalid report": "็„กๆ•ˆ็š„ๅ›žๅ ฑ",
+                "msg/invalid request": "็„กๆ•ˆ็š„่ฆๆฑ‚",
+                "msg/invalid sync payload": "็„กๆ•ˆ็š„ๅŒๆญฅ่ณ‡ๆ–™",
+                "msg/invalid task": "็„กๆ•ˆ็š„ๅทฅไฝœ",
+                "msg/invalid username": "ไฝฟ็”จ่€…ๅ็จฑ็„กๆ•ˆ",
+                "msg/is a directory": "้€™ๆ˜ฏ่ณ‡ๆ–™ๅคพ",
+                "msg/job cancelled": "ๅทฅไฝœๅทฒๅ–ๆถˆ",
+                "msg/job id required": "ๅฟ…้ ˆๆไพ›ๅทฅไฝœ ID",
+                "msg/job is limited to other nodes": "ๆญคๅทฅไฝœๅช่ƒฝๅœจๅ…ถไป–็ฏ€้ปžไธŠๅŸท่กŒ",
+                "msg/job not found": "ๆ‰พไธๅˆฐๅทฅไฝœ",
+                "msg/job owner is required": "ๅฟ…้ ˆๆไพ›ๅทฅไฝœๆ“ๆœ‰่€…",
+                "msg/job script is empty": "ๅทฅไฝœ่…ณๆœฌๆ˜ฏ็ฉบ็š„",
+                "msg/job timed out": "ๅทฅไฝœ้€พๆ™‚",
+                "msg/join response does not match the token's cluster": "ๅŠ ๅ…ฅๅ›žๆ‡‰่ˆ‡ๆฌŠๆ–ๆ‰€ๅฑฌ็š„ๅข้›†ไธ็ฌฆ",
+                "msg/join token has an invalid node URL": "ๅŠ ๅ…ฅๆฌŠๆ–ไธญ็š„็ฏ€้ปž็ถฒๅ€็„กๆ•ˆ",
+                "msg/join token is corrupted": "ๅŠ ๅ…ฅๆฌŠๆ–ๅทฒๆๆฏ€",
+                "msg/join token is for another cluster": "ๆญคๅŠ ๅ…ฅๆฌŠๆ–ๅฑฌๆ–ผๅฆไธ€ๅ€‹ๅข้›†",
+                "msg/join token is incomplete": "ๅŠ ๅ…ฅๆฌŠๆ–ไธๅฎŒๆ•ด",
+                "msg/join token is invalid or expired": "ๅŠ ๅ…ฅๆฌŠๆ–็„กๆ•ˆๆˆ–ๅทฒ้ŽๆœŸ",
+                "msg/join token required": "ๅฟ…้ ˆๆไพ›ๅŠ ๅ…ฅๆฌŠๆ–",
+                "msg/metadata store not available": "ไธญ็นผ่ณ‡ๆ–™ๅ„ฒๅญ˜ๅ€็„กๆณ•ไฝฟ็”จ",
+                "msg/no cluster volume available; add one in System Settings > Cluster Settings > Storage": "ๆฒ’ๆœ‰ๅฏ็”จ็š„ๅข้›†ๅ„ฒๅญ˜ๅท๏ผ›่ซ‹ๅœจใ€Œ็ณป็ตฑ่จญๅฎš > ๅข้›†่จญๅฎš > ๅ„ฒๅญ˜็ฉบ้–“ใ€ไธญๅŠ ๅ…ฅ",
+                "msg/no healthy copy of this file is reachable right now": "็›ฎๅ‰็„กๆณ•้€ฃ็ทšๅˆฐๆญคๆช”ๆกˆ็š„ไปปไฝ•ๅฅๅบทๅ‰ฏๆœฌ",
+                "msg/no node is available": "ๆฒ’ๆœ‰ๅฏ็”จ็š„็ฏ€้ปž",
+                "msg/node already has enough copies in flight": "ๆญค็ฏ€้ปžๅทฒๆœ‰่ถณๅค ็š„่ค‡่ฃฝๆญฃๅœจ้€ฒ่กŒ",
+                "msg/node id is required": "ๅฟ…้ ˆๆไพ›็ฏ€้ปž ID",
+                "msg/node id required": "ๅฟ…้ ˆๆไพ›็ฏ€้ปž ID",
+                "msg/node is DEGRADED": "็ฏ€้ปžๆ•ˆ่ƒฝ้™ไฝŽ",
+                "msg/node is DRAINING": "็ฏ€้ปžๆŽ’็ฉบไธญ",
+                "msg/node is MAINTENANCE": "็ฏ€้ปž็ถญ่ญทไธญ",
+                "msg/node is OFFLINE": "็ฏ€้ปžๅทฒ้›ข็ทš",
+                "msg/node is UNKNOWN": "็ฏ€้ปž็‹€ๆ…‹ไธๆ˜Ž",
+                "msg/node is degraded": "็ฏ€้ปžๆ•ˆ่ƒฝ้™ไฝŽ",
+                "msg/node is draining": "็ฏ€้ปžๆŽ’็ฉบไธญ",
+                "msg/node is maintenance": "็ฏ€้ปž็ถญ่ญทไธญ",
+                "msg/node is not available": "็ฏ€้ปž็„กๆณ•ไฝฟ็”จ",
+                "msg/node is offline": "็ฏ€้ปžๅทฒ้›ข็ทš",
+                "msg/node is unknown": "็ฏ€้ปž็‹€ๆ…‹ไธๆ˜Ž",
+                "msg/node lost while running the job": "ๅŸท่กŒๅทฅไฝœๆ™‚็ฏ€้ปžๅคฑๅŽป้€ฃ็ทš",
+                "msg/node name cannot be empty": "็ฏ€้ปžๅ็จฑไธๅฏ็‚บ็ฉบ",
+                "msg/node not found": "ๆ‰พไธๅˆฐ็ฏ€้ปž",
+                "msg/node refused this job earlier": "ๆญค็ฏ€้ปžๅ…ˆๅ‰ๆ‹’็ต•ไบ†้€™ๅ€‹ๅทฅไฝœ",
+                "msg/not a directory": "ไธๆ˜ฏ่ณ‡ๆ–™ๅคพ",
+                "msg/not a file": "ไธๆ˜ฏๆช”ๆกˆ",
+                "msg/not an ArozOS join token": "้€™ไธๆ˜ฏ ArozOS ๅŠ ๅ…ฅๆฌŠๆ–",
+                "msg/not in a cluster": "ๆœชๅŠ ๅ…ฅๅข้›†",
+                "msg/not logged in": "ๅฐšๆœช็™ปๅ…ฅ",
+                "msg/offset beyond end of file": "ไฝ็งป่ถ…ๅ‡บๆช”ๆกˆ็ตๅฐพ",
+                "msg/only the metadata leader assigns jobs": "ๅชๆœ‰ไธญ็นผ่ณ‡ๆ–™ไธปๅฐŽ็ฏ€้ปžๅฏไปฅๅˆ†้…ๅทฅไฝœ",
+                "msg/only the metadata leader assigns replication tasks": "ๅชๆœ‰ไธญ็นผ่ณ‡ๆ–™ไธปๅฐŽ็ฏ€้ปžๅฏไปฅๅˆ†้…่ค‡่ฃฝๅทฅไฝœ",
+                "msg/owner, script and at least one event type are required": "ๅฟ…้ ˆๆไพ›ๆ“ๆœ‰่€…ใ€่…ณๆœฌ่ˆ‡่‡ณๅฐ‘ไธ€็จฎไบ‹ไปถ้กžๅž‹",
+                "msg/path already exists": "่ทฏๅพ‘ๅทฒๅญ˜ๅœจ",
+                "msg/path escapes the volume": "่ทฏๅพ‘่ถ…ๅ‡บไบ†ๅ„ฒๅญ˜ๅท็ฏ„ๅœ",
+                "msg/path not found in the cluster namespace": "ๅœจๅข้›†ๅ‘ฝๅ็ฉบ้–“ไธญๆ‰พไธๅˆฐๆญค่ทฏๅพ‘",
+                "msg/path required": "ๅฟ…้ ˆ่ผธๅ…ฅ่ทฏๅพ‘",
+                "msg/peer has no reachable endpoint": "ๅฐๆ–น็ฏ€้ปžๆฒ’ๆœ‰ๅฏ้€ฃ็ทš็š„็ซฏ้ปž",
+                "msg/permission denied": "ๆฌŠ้™ไธ่ถณ",
+                "msg/previous node stopped reporting": "ๅ…ˆๅ‰็š„็ฏ€้ปžๅœๆญขๅ›žๅ ฑ",
+                "msg/refusing to delete the volume root": "ๆ‹’็ต•ๅˆช้™คๅ„ฒๅญ˜ๅทๆ น็›ฎ้Œ„",
+                "msg/refusing to drop the last healthy copy": "ๆ‹’็ต•ๅˆช้™คๆœ€ๅพŒไธ€ไปฝๅฅๅบทๅ‰ฏๆœฌ",
+                "msg/replicas must be between 0 and 16": "ๅ‰ฏๆœฌๆ•ธๅฟ…้ ˆไป‹ๆ–ผ 0 ๅˆฐ 16 ไน‹้–“",
+                "msg/replicas must be between 1 and 16": "ๅ‰ฏๆœฌๆ•ธๅฟ…้ ˆไป‹ๆ–ผ 1 ๅˆฐ 16 ไน‹้–“",
+                "msg/request is not signed": "่ฆๆฑ‚ๆœช็ฐฝ็ฝฒ",
+                "msg/request signature invalid": "่ฆๆฑ‚็š„็ฐฝ็ซ ็„กๆ•ˆ",
+                "msg/request targets another cluster": "่ฆๆฑ‚็š„็›ฎๆจ™ๆ˜ฏๅฆไธ€ๅ€‹ๅข้›†",
+                "msg/request timestamp outside the accepted window": "่ฆๆฑ‚็š„ๆ™‚้–“ๆˆณ่จ˜่ถ…ๅ‡บๅ…่จฑ็ฏ„ๅœ๏ผˆ่ซ‹ๆชขๆŸฅ็ฏ€้ปž็š„ๆ™‚้˜๏ผ‰",
+                "msg/requested log range was compacted, take a snapshot": "่ฆๆฑ‚็š„็ด€้Œ„็ฏ„ๅœๅทฒ่ขซๅฃ“็ธฎ๏ผŒ่ซ‹ๆ”น็”จๅฟซ็…ง",
+                "msg/script path required": "ๅฟ…้ ˆๆไพ›่…ณๆœฌ่ทฏๅพ‘",
+                "msg/sender is not a member of this cluster": "ๅ‚ณ้€่€…ไธๆ˜ฏๆญคๅข้›†็š„ๆˆๅ“ก",
+                "msg/shutting down": "ๆญฃๅœจ้—œ้–‰",
+                "msg/source copy is not readable": "ไพ†ๆบๅ‰ฏๆœฌ็„กๆณ•่ฎ€ๅ–",
+                "msg/target node is not connected through a tunnel on this node": "็›ฎๆจ™็ฏ€้ปžๆฒ’ๆœ‰้€้Žๆญค็ฏ€้ปž็š„้€š้“้€ฃ็ทš",
+                "msg/task is for another node": "ๆญคๅทฅไฝœๅฑฌๆ–ผๅฆไธ€ๅ€‹็ฏ€้ปž",
+                "msg/the URL answered but it is not an ArozOS cluster endpoint": "ๆญค็ถฒๅ€ๆœ‰ๅ›žๆ‡‰๏ผŒไฝ†ไธๆ˜ฏ ArozOS ๅข้›†็ซฏ้ปž",
+                "msg/the job has no dataset pattern": "ๆญคๅทฅไฝœๆฒ’ๆœ‰่ณ‡ๆ–™้›†ๆจฃๅผ",
+                "msg/the job owner has no account on this node": "ๆญค็ฏ€้ปžไธŠๆฒ’ๆœ‰ๅทฅไฝœๆ“ๆœ‰่€…็š„ๅธณ่™Ÿ",
+                "msg/the mappers emitted more than 8 MB; narrow the dataset or emit fewer values": "mapper ่ผธๅ‡บ่ถ…้Ž 8 MB๏ผ›่ซ‹็ธฎๅฐ่ณ‡ๆ–™้›†ๆˆ–ๆธ›ๅฐ‘่ผธๅ‡บ็š„ๅ€ผ",
+                "msg/the user root itself cannot be a cluster volume; pick a sub folder such as /cluster": "ไฝฟ็”จ่€…ๆ น็›ฎ้Œ„ๆœฌ่บซไธ่ƒฝไฝœ็‚บๅข้›†ๅ„ฒๅญ˜ๅท๏ผ›่ซ‹้ธๆ“‡ๅญ่ณ‡ๆ–™ๅคพ๏ผŒไพ‹ๅฆ‚ /cluster",
+                "msg/this folder is already a cluster volume": "ๆญค่ณ‡ๆ–™ๅคพๅทฒ็ถ“ๆ˜ฏๅข้›†ๅ„ฒๅญ˜ๅท",
+                "msg/this job script defines no run(job) function": "ๆญคๅทฅไฝœ่…ณๆœฌๆฒ’ๆœ‰ๅฎš็พฉ run(job) ๅ‡ฝๅผ",
+                "msg/this node cannot execute jobs": "ๆญค็ฏ€้ปž็„กๆณ•ๅŸท่กŒๅทฅไฝœ",
+                "msg/this node has no advertised URL; generate the join token on a node that other nodes can reach": "ๆญค็ฏ€้ปžๆฒ’ๆœ‰ๅฐๅค–็ถฒๅ€๏ผ›่ซ‹ๅœจๅ…ถไป–็ฏ€้ปžๅฏไปฅ้€ฃ็ทš็š„็ฏ€้ปžไธŠ็”ข็”ŸๅŠ ๅ…ฅๆฌŠๆ–",
+                "msg/this node is already part of a cluster, leave it first": "ๆญค็ฏ€้ปžๅทฒๅŠ ๅ…ฅๅข้›†๏ผŒ่ซ‹ๅ…ˆ้›ข้–‹",
+                "msg/this node is not part of a cluster": "ๆญค็ฏ€้ปžๅฐšๆœชๅŠ ๅ…ฅๅข้›†",
+                "msg/this node is not the identity origin of the cluster": "ๆญค็ฏ€้ปžไธๆ˜ฏๅข้›†็š„่บซๅˆ†ไพ†ๆบ",
+                "msg/this node is not the metadata leader": "ๆญค็ฏ€้ปžไธๆ˜ฏไธญ็นผ่ณ‡ๆ–™็š„ไธปๅฐŽ็ฏ€้ปž",
+                "msg/timed out waiting for the job to finish": "็ญ‰ๅพ…ๅทฅไฝœๅฎŒๆˆ้€พๆ™‚",
+                "msg/token id required": "ๅฟ…้ ˆๆไพ›ๆฌŠๆ– ID",
+                "msg/token not found": "ๆ‰พไธๅˆฐๆฌŠๆ–",
+                "msg/tunnel closed": "้€š้“ๅทฒ้—œ้–‰",
+                "msg/unknown upload session": "ๆœช็Ÿฅ็š„ไธŠๅ‚ณๅทฅไฝœ้šŽๆฎต",
+                "msg/unreachable": "็„กๆณ•้€ฃ็ทš",
+                "msg/unsupported node state": "ไธๆ”ฏๆด็š„็ฏ€้ปž็‹€ๆ…‹",
+                "msg/upload incomplete": "ไธŠๅ‚ณๆœชๅฎŒๆˆ",
+                "msg/url required": "ๅฟ…้ ˆ่ผธๅ…ฅ็ถฒๅ€",
+                "msg/use leave to remove this node": "่ฆ็งป้™คๆญค็ฏ€้ปž่ซ‹ไฝฟ็”จใ€Œ้›ข้–‹ๅข้›†ใ€",
+                "msg/user assertion expired": "ไฝฟ็”จ่€…่ฒๆ˜Žๅทฒ้ŽๆœŸ",
+                "msg/user assertion invalid": "ไฝฟ็”จ่€…่ฒๆ˜Ž็„กๆ•ˆ",
+                "msg/user not found on the identity origin": "่บซๅˆ†ไพ†ๆบไธŠๆ‰พไธๅˆฐๆญคไฝฟ็”จ่€…",
+                "msg/volume id required": "ๅฟ…้ ˆๆไพ›ๅ„ฒๅญ˜ๅท ID",
+                "msg/volume is not on this node": "ๅ„ฒๅญ˜ๅทไธๅœจๆญค็ฏ€้ปžไธŠ",
+                "msg/volume not found": "ๆ‰พไธๅˆฐๅ„ฒๅญ˜ๅท",
+                "msg/volume not found on this node": "ๆญค็ฏ€้ปžไธŠๆ‰พไธๅˆฐๅ„ฒๅญ˜ๅท",
+                "msgp/architecture {0} not allowed": "ไธๅ…่จฑ็š„ๆžถๆง‹ {0}",
+                "msgp/cancelled by {0}": "็”ฑ {0} ๅ–ๆถˆ",
+                "msgp/could not assemble the reduce input: {0}": "็„กๆณ•็ต„ๅˆ reduce ็š„่ผธๅ…ฅ๏ผš{0}",
+                "msgp/could not create a map task: {0}": "็„กๆณ•ๅปบ็ซ‹ map ๅญๅทฅไฝœ๏ผš{0}",
+                "msgp/could not create the reduce task: {0}": "็„กๆณ•ๅปบ็ซ‹ reduce ๅญๅทฅไฝœ๏ผš{0}",
+                "msgp/drive {0} is not a local drive on this node": "็ฃ็ขŸ {0} ไธๆ˜ฏๆญค็ฏ€้ปžไธŠ็š„ๆœฌๆฉŸ็ฃ็ขŸ",
+                "msgp/hook script not found: {0}": "ๆ‰พไธๅˆฐๆŽ›้‰ค่…ณๆœฌ๏ผš{0}",
+                "msgp/invalid join response from {0}": "{0} ็š„ๅŠ ๅ…ฅๅ›žๆ‡‰็„กๆ•ˆ",
+                "msgp/invalid node key encoding: {0}": "็ฏ€้ปž้‡‘้‘ฐ็ทจ็ขผ็„กๆ•ˆ๏ผš{0}",
+                "msgp/invalid public key encoding: {0}": "ๅ…ฌ้–‹้‡‘้‘ฐ็ทจ็ขผ็„กๆ•ˆ๏ผš{0}",
+                "msgp/job timed out after {0}s": "ๅทฅไฝœๅœจ {0} ็ง’ๅพŒ้€พๆ™‚",
+                "msgp/local drive {0} is not mounted": "ๆœฌๆฉŸ็ฃ็ขŸ {0} ๆœชๆŽ›่ผ‰",
+                "msgp/missing feature {0}": "็ผบๅฐ‘ๅŠŸ่ƒฝ {0}",
+                "msgp/no cluster volume available; add one in System Settings > Cluster Settings > Storage ({0})": "ๆฒ’ๆœ‰ๅฏ็”จ็š„ๅข้›†ๅ„ฒๅญ˜ๅท๏ผ›่ซ‹ๅœจใ€Œ็ณป็ตฑ่จญๅฎš > ๅข้›†่จญๅฎš > ๅ„ฒๅญ˜็ฉบ้–“ใ€ไธญๅŠ ๅ…ฅ๏ผˆ{0}๏ผ‰",
+                "msgp/no reachable copy of: {0}": "็„กๆณ•้€ฃ็ทšๅˆฐไธ‹ๅˆ—ๆช”ๆกˆ็š„ๅ‰ฏๆœฌ๏ผš{0}",
+                "msgp/node {0} could not take the job: {1}": "็ฏ€้ปž {0} ็„กๆณ•ๆŽฅๆ”ถๆญคๅทฅไฝœ๏ผš{1}",
+                "msgp/os {0} not allowed": "ไธๅ…่จฑ็š„ไฝœๆฅญ็ณป็ตฑ {0}",
+                "msgp/permission denied: {0}": "ๆฌŠ้™ไธ่ถณ๏ผš{0}",
+                "msgp/remote node returned {0}: {1}": "้ ็ซฏ็ฏ€้ปžๅ›žๅ‚ณ {0}๏ผš{1}",
+                "msgp/script not found: {0}": "ๆ‰พไธๅˆฐ่…ณๆœฌ๏ผš{0}",
+                "msgp/this volume holds the only copy of {0} file(s); evacuate it first": "ๆญคๅ„ฒๅญ˜ๅทๆŒๆœ‰ {0} ๅ€‹ๆช”ๆกˆ็š„ๅ”ฏไธ€ๅ‰ฏๆœฌ๏ผ›่ซ‹ๅ…ˆๆ’ค้›ข",
+                "msgp/unable to reach {0}: {1}": "็„กๆณ•้€ฃ็ทšๅˆฐ {0}๏ผš{1}",
+                "msgp/unreachable: {0}": "็„กๆณ•้€ฃ็ทš๏ผš{0}",
+                "msgp/unsupported node URL scheme: {0}": "ไธๆ”ฏๆด็š„็ฏ€้ปž็ถฒๅ€ๅ”ๅฎš๏ผš{0}",
+                "msgp/weight {0} must be a number": "ๆฌŠ้‡ {0} ๅฟ…้ ˆๆ˜ฏๆ•ธๅญ—",
+                "msgp/weight {0} must be between 0 and 1": "ๆฌŠ้‡ {0} ๅฟ…้ ˆไป‹ๆ–ผ 0 ่ˆ‡ 1 ไน‹้–“",
+                "msgp/{0} produced an unreadable result": "{0} ็”ข็”Ÿไบ†็„กๆณ•่ฎ€ๅ–็š„็ตๆžœ"
+            },
+            "placeholder": {
+                "Name (optional)": "ๅ็จฑ๏ผˆ้ธๅกซ๏ผ‰",
+                "job id": "ๅทฅไฝœ ID",
+                "Nightly index": "ๆฏๆ™š็ดขๅผ•",
+                "50 per task": "ๆฏๅ€‹ๅญๅทฅไฝœ 50 ๅ€‹"
+            },
+            "titles": {
+                "Re-detect capabilities": "้‡ๆ–ฐๅตๆธฌๅŠŸ่ƒฝ"
+            }
+        },
+        "zh-hk": {
+            "name": "็น้ซ”ไธญๆ–‡๏ผˆ้ฆ™ๆธฏ๏ผ‰",
+            "fontFamily": "\"Microsoft JhengHei\",\"SimHei\", \"Apple LiGothic Medium\", \"STHeiti\"",
+            "strings": {
+                "cluster/ago/d": "{0} ๅคฉๅ‰",
+                "cluster/ago/h": "{0} ๅฐๆ™‚ๅ‰",
+                "cluster/ago/m": "{0} ๅˆ†้˜ๅ‰",
+                "cluster/ago/s": "{0} ็ง’ๅ‰",
+                "cluster/auto": "่‡ชๅ‹•๏ผˆไปปไธ€ๅฏ้€ฃ็ทš็š„ๆˆๅ“ก๏ผ‰",
+                "cluster/cancel": "ๅ–ๆถˆ",
+                "cluster/caps": "ๅŠŸ่ƒฝ",
+                "cluster/cfg/insecure": "ๆŽฅๅ—ๅ…ถไป–็ฏ€้ปž็š„่‡ช็ฐฝๆ†‘่ญ‰๏ผˆๅƒ…้™ๅ€ๅŸŸ็ถฒ่ทฏ๏ผ‰",
+                "cluster/cfg/name": "็ฏ€้ปžๅ็จฑ",
+                "cluster/cfg/test": "ๆธฌ่ฉฆ",
+                "cluster/cfg/tunnel": "็ถ“็”ฑ้€š้“้€ฃ็ทš",
+                "cluster/cfg/url": "ๅฐๅค–็ถฒๅ€๏ผˆๅ…ถไป–็ฏ€้ปž้€ฃ็ทšๅˆฐๆญค็ฏ€้ปž็š„ๆ–นๅผ๏ผ‰",
+                "cluster/cfg/urlhint": "่‹ฅๆญค็ฏ€้ปžๆฒ’ๆœ‰ๅ…ฌ้–‹ไฝๅ€่ซ‹็•™็ฉบใ€‚ๅฎƒๆœƒๆ”น็‚บๅฐไธ€ๅ€‹ๅฏ้€ฃ็ทš็š„ๆˆๅ“กไฟๆŒ้€š้“้€ฃ็ทš๏ผŒ้€™ไนŸ่ƒฝ็ฉฟ่ถŠ Cloudflareใ€‚",
+                "cluster/clusterid": "ๅข้›† ID",
+                "cluster/confirmleave": "่ฆ้›ข้–‹ๅข้›†ๅ—Ž๏ผŸไน‹ๅพŒๅฐ‡็„กๆณ•ๅพž้€™่ฃกๅญ˜ๅ–ๅ…ถไป–็ฏ€้ปžไธŠ็š„ๆช”ๆกˆ่ˆ‡ๅทฅไฝœใ€‚",
+                "cluster/confirmremove": "่ฆ็งป้™ค็ฏ€้ปžใ€Œ{0}ใ€ๅ—Ž๏ผŸ",
+                "cluster/copied": "ๅทฒๅฐ‡ๆฌŠๆ–่ค‡่ฃฝๅˆฐๅ‰ช่ฒผ็ฐฟ",
+                "cluster/copy": "่ค‡่ฃฝ",
+                "cluster/coresvalue": "{0} ๆ ธๅฟƒ",
+                "cluster/create": "ๅปบ็ซ‹ๆ–ฐๅข้›†",
+                "cluster/createbtn": "ๅปบ็ซ‹",
+                "cluster/created": "ๅปบ็ซ‹ๆ™‚้–“",
+                "cluster/createhint": "ๆญค็ฏ€้ปžๆœƒๆˆ็‚บ็ฌฌไธ€ๅ€‹ๆˆๅ“กใ€‚ไน‹ๅพŒๅœจ้€™่ฃก็”ข็”ŸๅŠ ๅ…ฅๆฌŠๆ–ไปฅๅŠ ๅ…ฅๆ›ดๅคš็ฏ€้ปžใ€‚",
+                "cluster/direct": "็›ด้€ฃ",
+                "cluster/disk": "็ฃ็ขŸ",
+                "cluster/diskfree": "ๅ‰ฉ้ค˜ {0}๏ผŒๅ…ฑ {1}",
+                "cluster/drain": "ๆŽ’็ฉบ",
+                "cluster/entername": "่ซ‹่ผธๅ…ฅๅข้›†ๅ็จฑ",
+                "cluster/entertoken": "่ซ‹่ฒผไธŠๅŠ ๅ…ฅๆฌŠๆ–",
+                "cluster/enterurl": "่ซ‹่ผธๅ…ฅ่ฆๆธฌ่ฉฆ็š„็ถฒๅ€",
+                "cluster/expiresat": "ๆ–ผ {0} ๅˆฐๆœŸ",
+                "cluster/folder": "่ณ‡ๆ–™ๅคพ",
+                "cluster/freeamount": "ๅ‰ฉ้ค˜ {0}",
+                "cluster/gentoken": "็”ข็”ŸๅŠ ๅ…ฅๆฌŠๆ–",
+                "cluster/id/accounts": "ๅธณ่™Ÿ",
+                "cluster/id/apply": "ๅฅ—็”จ",
+                "cluster/id/conflicts": "ๅƒ…้™ๆœฌๆฉŸ",
+                "cluster/id/conflictswhy": "ๅŒๅ็š„ๆœฌๆฉŸๅธณ่™Ÿๆœƒ็ถญๆŒๅŽŸ็‹€",
+                "cluster/id/disabled": "ๅœ็”จ๏ผˆๆฏๅ€‹็ฏ€้ปžๅ„่‡ช้ฉ—่ญ‰่‡ชๅทฑ็š„ๅธณ่™Ÿ๏ผ‰",
+                "cluster/id/fallback": "็„กๆณ•้€ฃ็ทšๅˆฐ่บซๅˆ†ไพ†ๆบ๏ผŒๅทฒๆ”น็”จๆœฌๆฉŸๅธณ่™Ÿ",
+                "cluster/id/forward": "่ฝ‰้€",
+                "cluster/id/hint": "่บซๅˆ†ไพ†ๆบๆœƒ้ฉ—่ญ‰ๅข้›†ไธญ็š„ๆฏไธ€ๆฌก็™ปๅ…ฅใ€‚ๅธณ่™Ÿๆœƒ่ค‡่ฃฝๅˆฐๅ…ถไป–็ฏ€้ปž๏ผŒๅ› ๆญคๅœจ่บซๅˆ†ไพ†ๆบ็„กๆณ•้€ฃ็ทšๆ™‚ไปๅฏไฝฟ็”จใ€‚็พค็ต„ไปฅๅ็จฑๅฐๆ‡‰๏ผŒ่ซ‹ๅœจๆฏๅ€‹็ฏ€้ปžไธŠๅปบ็ซ‹็›ธๅŒ็š„ๆฌŠ้™็พค็ต„ใ€‚",
+                "cluster/id/lastlogin": "ไธŠๆฌก็™ปๅ…ฅๆชขๆŸฅ",
+                "cluster/id/lastsync": "ไธŠๆฌกๅŒๆญฅ",
+                "cluster/id/local": "ๆฏๅ€‹็ฏ€้ปžไปฅ่‡ชๅทฑ็š„ๅธณ่™Ÿ้ฉ—่ญ‰็™ปๅ…ฅ",
+                "cluster/id/mirrors": "{1} ๅ€‹ๆœฌๆฉŸๅธณ่™Ÿไธญๆœ‰ {0} ๅ€‹่ˆ‡่บซๅˆ†ไพ†ๆบๅŒๆญฅ",
+                "cluster/id/mode": "ๆจกๅผ",
+                "cluster/id/origin": "ไพ†ๆบ",
+                "cluster/id/originhere": "ๆญค็ฏ€้ปž่ฒ ่ฒฌ้ฉ—่ญ‰ๆ•ดๅ€‹ๅข้›†็š„็™ปๅ…ฅ",
+                "cluster/id/published": "ๅทฒ็™ผๅธƒ {0} ๅ€‹็ตฆๆˆๅ“ก",
+                "cluster/id/replicated": "ๅทฒ่ค‡่ฃฝ",
+                "cluster/id/skipped": "ๅทฒ็•ฅ้Ž",
+                "cluster/id/skippedwhy": "ๆญค็ฏ€้ปžไธŠๆฒ’ๆœ‰ไป–ๅ€‘ๆ‰€ๅฑฌ็š„ไปปไฝ•็พค็ต„",
+                "cluster/id/sync": "็ซ‹ๅณๅŒๆญฅ",
+                "cluster/id/synced": "ๅธณ่™ŸๅทฒๅŒๆญฅ",
+                "cluster/id/title": "่บซๅˆ†๏ผˆๅ–ฎไธ€็™ปๅ…ฅ๏ผ‰",
+                "cluster/id/verifiedby": "็™ปๅ…ฅ็”ฑ {0} ้ฉ—่ญ‰",
+                "cluster/info/novolume": "ๅข้›†ๅฐšๆœช่จญๅฎšๅ„ฒๅญ˜็ฉบ้–“ใ€‚",
+                "cluster/info/novolumehint": "ๅœจๅข้›†่จญๅฎšไธญๅŠ ๅ…ฅๅ„ฒๅญ˜ๅท๏ผŒcluster:/ ็ฃ็ขŸๆ‰ๆœƒๅ‡บ็พๅœจๆช”ๆกˆ็ฎก็†ๅ™จไธญใ€‚",
+                "cluster/info/standalone": "ๆญค็ฏ€้ปžๅฐšๆœชๅŠ ๅ…ฅๅข้›†ใ€‚",
+                "cluster/info/standalonehint": "่ซ‹ๅœจๅข้›†่จญๅฎšไธญๅปบ็ซ‹ๆˆ–ๅŠ ๅ…ฅๅข้›†ใ€‚ๅœจๆญคไน‹ๅ‰ cluster:/ ็ฃ็ขŸๆœƒไฟๆŒ้šฑ่—ใ€‚",
+                "cluster/info/tagline": "ๆญค็ฏ€้ปžใ€ๅ…ถไป–็ฏ€้ปžใ€ๅ‘ฝๅ็ฉบ้–“่ˆ‡ๆŽ’็จ‹ๅ™จ็š„็‹€ๆ…‹",
+                "cluster/info/title": "ๅข้›†่ณ‡่จŠ",
+                "cluster/invite": "ๅŠ ๅ…ฅ็ฏ€้ปž",
+                "cluster/jobs/args": "ๅƒๆ•ธ๏ผˆJSON๏ผŒ้ธๅกซ๏ผ‰",
+                "cluster/jobs/attemptn": "็ฌฌ {0} ๆฌกๅ˜—่ฉฆ",
+                "cluster/jobs/browse": "็€่ฆฝ",
+                "cluster/jobs/cancel": "ๅ–ๆถˆ",
+                "cluster/jobs/complete": "ๅทฒๅฎŒๆˆ {0} / {1}",
+                "cluster/jobs/dataset": "่ณ‡ๆ–™้›†",
+                "cluster/jobs/datasethint": "ไฝฟ็”จ่ณ‡ๆ–™้›†ๆ™‚๏ผŒ่…ณๆœฌๅฟ…้ ˆๅฎš็พฉ mapper(files, emit) ่ˆ‡ reducer(key, values)๏ผ›ๆช”ๆกˆๆœƒๅœจๆŒๆœ‰ๅฎƒๅ€‘็š„็ฏ€้ปžไธŠ่™•็†ใ€‚",
+                "cluster/jobs/datasetlabel": "Map/reduce ่ณ‡ๆ–™้›†๏ผˆ้ธๅกซ็š„่ฌ็”จๅญ—ๅ…ƒ๏ผ›ๆœƒๅฐ‡ๆญคๅทฅไฝœ่ฎŠๆˆ map/reduce ๅทฅไฝœ๏ผ‰",
+                "cluster/jobs/detail": "ๅทฅไฝœ่ฉณ็ดฐ่ณ‡ๆ–™",
+                "cluster/jobs/duration": "่€—ๆ™‚",
+                "cluster/jobs/failedn": "{0} ๅ€‹ๅคฑๆ•—",
+                "cluster/jobs/features": "ๅฟ…่ฆๅŠŸ่ƒฝ",
+                "cluster/jobs/inputs": "่ผธๅ…ฅ่ทฏๅพ‘๏ผˆไปฅ้€—่™Ÿๅˆ†้š”๏ผŒ็”จๆ–ผๅœจ่ณ‡ๆ–™้™„่ฟ‘ๅŸท่กŒ๏ผ‰",
+                "cluster/jobs/job": "ๅทฅไฝœ",
+                "cluster/jobs/jobs": "ๅทฅไฝœ",
+                "cluster/jobs/kind/map": "map ๅญๅทฅไฝœ",
+                "cluster/jobs/kind/mapreduce": "map/reduce",
+                "cluster/jobs/kind/reduce": "reduce ๅญๅทฅไฝœ",
+                "cluster/jobs/log": "็ด€้Œ„",
+                "cluster/jobs/minsecs": "{0} ๅˆ† {1} ็ง’",
+                "cluster/jobs/name": "ๅ็จฑ",
+                "cluster/jobs/needscript": "ๅฟ…้ ˆๆŒ‡ๅฎš่…ณๆœฌ่ทฏๅพ‘",
+                "cluster/jobs/node": "็ฏ€้ปž",
+                "cluster/jobs/nodetail": "้ธๆ“‡ไธ€ๅ€‹ๅทฅไฝœไปฅๆŸฅ็œ‹ๅ…ถ็ด€้Œ„่ˆ‡่ผธๅ‡บ",
+                "cluster/jobs/none": "ๅฐš็„กๅทฅไฝœ",
+                "cluster/jobs/output": "่ผธๅ‡บ",
+                "cluster/jobs/owner": "ๆ“ๆœ‰่€…",
+                "cluster/jobs/progress": "้€ฒๅบฆ",
+                "cluster/jobs/queued": "ๅทฅไฝœๅทฒๆŽ’ๅ…ฅไฝ‡ๅˆ—",
+                "cluster/jobs/run": "ๅŸท่กŒๅทฅไฝœ",
+                "cluster/jobs/scheduler": "ๆŽ’็จ‹ๅ™จ๏ผš{0}",
+                "cluster/jobs/script": "่…ณๆœฌ",
+                "cluster/jobs/scriptlabel": "่…ณๆœฌ๏ผˆๅฎš็พฉ run(job) ็š„ .agi ๆช”ๆกˆ๏ผ‰",
+                "cluster/jobs/secs": "{0} ็ง’",
+                "cluster/jobs/slots": "ๆญค็ฏ€้ปž {0}/{1} ๅ€‹ๅŸท่กŒๆงฝๅฟ™็ขŒไธญ",
+                "cluster/jobs/status": "็‹€ๆ…‹",
+                "cluster/jobs/submit": "ๆไบคๅทฅไฝœ",
+                "cluster/jobs/submithint": "่…ณๆœฌๆœƒไปฅไฝ ็š„ๆฌŠ้™ๅœจ้ธๅฎš็š„็ฏ€้ปžไธŠๅŸท่กŒใ€‚cluster:/ ่ทฏๅพ‘็…งๅธธๅฏ็”จใ€‚",
+                "cluster/jobs/summary": "{0} ๅ€‹ๅŸท่กŒไธญ๏ผŒ{1} ๅ€‹ๆŽ’้šŠไธญ๏ผŒ{2} ๅ€‹ๅทฒๅฎŒๆˆ",
+                "cluster/jobs/tagline": "ๅœจๆœ€้ฉๅˆ็š„็ฏ€้ปžไธŠๅŸท่กŒ่…ณๆœฌ",
+                "cluster/jobs/tasks": "ๅญๅทฅไฝœ",
+                "cluster/jobs/timeout": "้€พๆ™‚๏ผˆ็ง’๏ผ‰",
+                "cluster/jobs/title": "ๅข้›†ๅทฅไฝœ",
+                "cluster/jobs/typepath": "่ซ‹่ผธๅ…ฅ่…ณๆœฌ่ทฏๅพ‘๏ผŒไพ‹ๅฆ‚ user:/jobs/index.agi",
+                "cluster/jobs/waiting": "็ญ‰ๅพ…ๅŽŸๅ› ",
+                "cluster/join": "ๅŠ ๅ…ฅ็พๆœ‰ๅข้›†",
+                "cluster/joinbtn": "ๅŠ ๅ…ฅ",
+                "cluster/joinhint": "่ฒผไธŠๅœจๅข้›†ไธญๅฏ้€ฃ็ทšๆˆๅ“กไธŠ็”ข็”Ÿ็š„ๅŠ ๅ…ฅๆฌŠๆ–ใ€‚",
+                "cluster/joining": "ๆญฃๅœจๅŠ ๅ…ฅ...",
+                "cluster/justnow": "ๅ‰›ๅ‰›",
+                "cluster/key": "็ฏ€้ปž้‡‘้‘ฐ",
+                "cluster/lastseen": "ๆœ€ๅพŒ้€ฃ็ทš",
+                "cluster/leave": "้›ข้–‹ๅข้›†",
+                "cluster/link/info": "ๅข้›†่ณ‡่จŠ",
+                "cluster/link/jobs": "ๅข้›†ๅทฅไฝœ",
+                "cluster/link/settings": "ๅข้›†่จญๅฎš",
+                "cluster/load": "่ฒ ่ผ‰",
+                "cluster/maintenance": "็ถญ่ญท",
+                "cluster/members": "ๆˆๅ“ก",
+                "cluster/membershint": "็ถญ่ญทๆจกๅผๆœƒ่ฎ“็ฏ€้ปž้€€ๅ‡บๆŽ’็จ‹ใ€‚ๆŽ’็ฉบ้‚„ๆœƒๆŠŠๅฎƒ็š„ๅ‰ฏๆœฌ็งปๅˆฐๅ…ถไป–็ฏ€้ปž๏ผŒไน‹ๅพŒๅณๅฏๅฎ‰ๅ…จ้—œๆฉŸใ€‚",
+                "cluster/meta/entries": "้ …็›ฎ",
+                "cluster/meta/entriesvalue": "{0} ๅ€‹ๆช”ๆกˆใ€{1} ๅ€‹่ณ‡ๆ–™ๅคพใ€{2} ๅ€‹ๅ„ฒๅญ˜ๅท",
+                "cluster/meta/leader": "ไธปๅฐŽ็ฏ€้ปž",
+                "cluster/meta/log": "็ด€้Œ„",
+                "cluster/meta/noleader": "้ธ่ˆ‰ไธญ",
+                "cluster/meta/pending": "{0} ็ญ†ๅพ…่™•็†",
+                "cluster/meta/term": "ไปปๆœŸ {0}",
+                "cluster/natonly": "็„ก็ถฒๅ€",
+                "cluster/never": "ๅพžๆœช",
+                "cluster/node": "็ฏ€้ปž",
+                "cluster/nodes": "็ฏ€้ปž",
+                "cluster/nonodes": "ๆฒ’ๆœ‰็ฏ€้ปž",
+                "cluster/noreport": "ๅฐš็„กๅ›žๅ ฑ",
+                "cluster/noroute": "็„ก่ทฏๅพ‘",
+                "cluster/notmember": "ๅฐšๆœชๅŠ ๅ…ฅๅข้›†",
+                "cluster/notunnel": "็„ก้€š้“",
+                "cluster/now": "็พๅœจ",
+                "cluster/platform": "ๅนณๅฐ",
+                "cluster/platformline": "{0} / {1}๏ผŒ{2} ๆ ธๅฟƒ",
+                "cluster/pol/1": "1 ไปฝๅ‰ฏๆœฌ",
+                "cluster/pol/2": "2 ไปฝๅ‰ฏๆœฌ",
+                "cluster/pol/3": "3 ไปฝๅ‰ฏๆœฌ",
+                "cluster/pol/4": "4 ไปฝๅ‰ฏๆœฌ",
+                "cluster/pol/copies": "{0} ไปฝๅ‰ฏๆœฌ",
+                "cluster/pol/hint": "ๆฒ’ๆœ‰่จญๅฎšๅŽŸๅ‰‡็š„่ณ‡ๆ–™ๅคพๅชไฟ็•™ไธ€ไปฝๅ‰ฏๆœฌใ€‚้กๅค–็š„ๅ‰ฏๆœฌ็”ฑ่ค‡่ฃฝๆœๅ‹™ๅปบ็ซ‹ใ€‚",
+                "cluster/pol/label": "ๆฏๅ€‹ๆœ€ไธŠๅฑค่ณ‡ๆ–™ๅคพ็š„ๅ‰ฏๆœฌๆ•ธ",
+                "cluster/pol/set": "่จญๅฎš",
+                "cluster/pol/title": "ๅ‰ฏๆœฌๅŽŸๅ‰‡",
+                "cluster/probe": "ๆŽขๆธฌ",
+                "cluster/probefail": "{0}๏ผš{1}๏ผˆ{2}๏ผ‰",
+                "cluster/probeok": "{0}๏ผš{1} ๆฏซ็ง’๏ผˆ{2}๏ผ‰",
+                "cluster/reach": "้€ฃ็ทšๆ–นๅผ",
+                "cluster/reachableok": "ๅฏ้€ฃ็ทš๏ผŒๅพ€่ฟ” {0} ๆฏซ็ง’",
+                "cluster/redetect": "้‡ๆ–ฐๅตๆธฌ",
+                "cluster/remove": "็งป้™ค",
+                "cluster/repl/evacuating": "{0} ๅ€‹ๅ„ฒๅญ˜ๅทๆ’ค้›ขไธญ",
+                "cluster/repl/files": "ๆช”ๆกˆ",
+                "cluster/repl/fromto": "{0} ๅˆฐ {1}",
+                "cluster/repl/healthy": "ๆ‰€ๆœ‰ๆช”ๆกˆ็š†็ฌฆๅˆๅŽŸๅ‰‡",
+                "cluster/repl/inflight": "้€ฒ่กŒไธญ",
+                "cluster/repl/inflightvalue": "{0} ๅ€‹ๅŸท่กŒไธญ๏ผŒ{1} ๅ€‹ๆŽ’้šŠไธญ",
+                "cluster/repl/inspect": "ๆชขๆŸฅๆช”ๆกˆ",
+                "cluster/repl/inspectbtn": "้กฏ็คบๅ‰ฏๆœฌ",
+                "cluster/repl/issues": "{0} ๅ€‹ๅ•้กŒ",
+                "cluster/repl/lastrun": "ไธŠๆฌกๅŸท่กŒ {0}",
+                "cluster/repl/nocopy": "็„กๅฅๅบทๅ‰ฏๆœฌ",
+                "cluster/repl/nolocations": "ๆฒ’ๆœ‰ๅ‰ฏๆœฌ็ด€้Œ„",
+                "cluster/repl/notasks": "ๆฒ’ๆœ‰่ค‡่ฃฝๅทฅไฝœ",
+                "cluster/repl/notfound": "ๅœจๅ‘ฝๅ็ฉบ้–“ไธญๆ‰พไธๅˆฐๆญค่ทฏๅพ‘",
+                "cluster/repl/over": "ๅคš้ค˜ๅ‰ฏๆœฌ",
+                "cluster/repl/plan": "็ซ‹ๅณไฟฎๅพฉ",
+                "cluster/repl/planned": "่ฆๅŠƒๅ™จๅทฒๅŸท่กŒ๏ผš{0} ๅ€‹ๆŽ’ๅ…ฅ๏ผŒ{1} ๅ€‹ๅˆช้™ค๏ผŒ{2} ๅ€‹ๆจ™่จ˜็‚บ้ŽๆœŸ",
+                "cluster/repl/planner": "่ฆๅŠƒๅ™จ",
+                "cluster/repl/primary": "ไธป่ฆ",
+                "cluster/repl/reason/evacuate": "ๆ’ค้›ข",
+                "cluster/repl/reason/repair": "ไฟฎๅพฉ",
+                "cluster/repl/reason/replica": "ๆ–ฐๅขžๅ‰ฏๆœฌ",
+                "cluster/repl/route": "ไพ†ๆบ / ็›ฎ็š„ๅœฐ",
+                "cluster/repl/stale": "้ŽๆœŸๅ‰ฏๆœฌ",
+                "cluster/repl/task": "ๅทฅไฝœ",
+                "cluster/repl/title": "ๅ‰ฏๆœฌ่ค‡่ฃฝ",
+                "cluster/repl/under": "ๅ‰ฏๆœฌไธ่ถณ",
+                "cluster/repl/verified": "ๅทฒ้ฉ—่ญ‰ {0} ไปฝๅ‰ฏๆœฌ๏ผˆ{1}๏ผ‰๏ผŒ{2} ไปฝ้ŽๆœŸ",
+                "cluster/repl/verify": "้ฉ—่ญ‰ๆญค็ฏ€้ปž",
+                "cluster/requestfailed": "่ฆๆฑ‚ๅคฑๆ•—",
+                "cluster/resume": "ๆขๅพฉ",
+                "cluster/revoke": "ๆ’ค้Šท",
+                "cluster/route": "่ทฏๅพ‘",
+                "cluster/route/direct": "็›ด้€ฃ",
+                "cluster/route/none": "็„ก่ทฏๅพ‘",
+                "cluster/route/relay": "็ถ“็”ฑ {0} ่ฝ‰้€",
+                "cluster/route/tunnel": "้€š้“",
+                "cluster/save": "ๅ„ฒๅญ˜",
+                "cluster/saved": "ๅทฒๅ„ฒๅญ˜",
+                "cluster/sched/cpu": "CPU ้–’็ฝฎ",
+                "cluster/sched/cpu/hint": "ๅ„ชๅ…ˆ้ธๆ“‡้–’็ฝฎ็š„่™•็†ๅ™จ",
+                "cluster/sched/degraded": "ๅฅๅบท็‹€ๆ…‹",
+                "cluster/sched/degraded/hint": "้ฟ้–‹ๆ•ˆ่ƒฝไธไฝณ็š„็ฏ€้ปž",
+                "cluster/sched/disk": "ๅฏ็”จ็ฃ็ขŸ",
+                "cluster/sched/disk/hint": "ๅ„ชๅ…ˆ้ธๆ“‡ๆœ‰ๆˆ้•ท็ฉบ้–“็š„็ฏ€้ปž",
+                "cluster/sched/diskfree": "็ฃ็ขŸๅ‰ฉ้ค˜",
+                "cluster/sched/diversity": "็ซ™้ปžๅˆ†ๆ•ฃ",
+                "cluster/sched/diversity/hint": "ๆŠŠ็ฌฌไบŒไปฝๅ‰ฏๆœฌๆ”พๅœจ้›ข็ฌฌไธ€ไปฝ่ผƒ้ ็š„ๅœฐๆ–น๏ผŒ้€™ๆจฃๆŸๅ€‹็ซ™้ปžๅœๆ“บๆ™‚ไธๆœƒ้บๅคฑ่ณ‡ๆ–™",
+                "cluster/sched/explain": "ๅทฅไฝœ็‚บไฝ•่ขซๅˆ†้…ๅˆฐ้‚ฃ่ฃก๏ผŸ",
+                "cluster/sched/explainbtn": "่ชชๆ˜Ž",
+                "cluster/sched/explainhint": "ๅพžๅข้›†ๅทฅไฝœ้ ้ข่ค‡่ฃฝๅทฅไฝœ ID๏ผŒๅณๅฏๆŸฅ็œ‹ๅˆ†้…ๆ™‚็š„ๆŽ’ๅใ€‚",
+                "cluster/sched/factor/cpu free": "CPU ้–’็ฝฎ",
+                "cluster/sched/factor/degraded": "ๆ•ˆ่ƒฝ้™ไฝŽ",
+                "cluster/sched/factor/disk free": "็ฃ็ขŸๅ‰ฉ้ค˜",
+                "cluster/sched/factor/locality": "่ณ‡ๆ–™ไฝ็ฝฎ",
+                "cluster/sched/factor/memory free": "่จ˜ๆ†ถ้ซ”ๅ‰ฉ้ค˜",
+                "cluster/sched/factor/network distance": "็ถฒ่ทฏ่ท้›ข",
+                "cluster/sched/factor/preferred features": "ๅๅฅฝๅŠŸ่ƒฝ",
+                "cluster/sched/factor/queue depth": "ไฝ‡ๅˆ—้•ทๅบฆ",
+                "cluster/sched/factor/site diversity": "็ซ™้ปžๅˆ†ๆ•ฃ",
+                "cluster/sched/features": "{0} ้ …ๅŠŸ่ƒฝ",
+                "cluster/sched/from": "ไพ†ๆบ / ็›ฎ็š„ๅœฐ",
+                "cluster/sched/here": "ๆœฌๆฉŸ",
+                "cluster/sched/hint": "ๅทฅไฝœใ€ๆ–ฐๆช”ๆกˆ่ˆ‡้กๅค–ๅ‰ฏๆœฌๆœƒไบค็ตฆๅˆ†ๆ•ธๆœ€้ซ˜็š„็ฏ€้ปžใ€‚่ชฟ้ซ˜ๆฌŠ้‡ๅฏ่ฎ“่ฉฒ้ …็‰น่ณชๆ›ด้‡่ฆใ€‚ๅฏฆ้š›ๅˆ†ๆ•ธ้กฏ็คบๅœจๅข้›†่ณ‡่จŠ้ ้ขใ€‚",
+                "cluster/sched/infohint": "ไพๅข้›†่จญๅฎšไธญ็š„ๆฌŠ้‡๏ผŒๅ„็ฏ€้ปž็›ฎๅ‰็š„ๅˆ†ๆ•ธใ€‚ๅทฅไฝœใ€ๆ–ฐๆช”ๆกˆ่ˆ‡้กๅค–ๅ‰ฏๆœฌๆœƒไบค็ตฆๅˆ†ๆ•ธๆœ€้ซ˜่€…ใ€‚",
+                "cluster/sched/latency": "็ถฒ่ทฏ่ท้›ข",
+                "cluster/sched/latency/hint": "้ฟ้–‹่ท้›ข่ณ‡ๆ–™ๅคช้ ็š„็ฏ€้ปž",
+                "cluster/sched/locality": "่ณ‡ๆ–™ไฝ็ฝฎ",
+                "cluster/sched/locality/hint": "ๅ„ชๅ…ˆ้ธๆ“‡ๅทฒๆŒๆœ‰่ณ‡ๆ–™็š„็ฏ€้ปž",
+                "cluster/sched/matrixhint": "ๆฏๅฐ็ฏ€้ปžไน‹้–“็š„ๅพ€่ฟ”ๆ™‚้–“๏ผˆๆฏซ็ง’๏ผ‰ใ€‚ๆช”ๆกˆ็š„ๅ‰ฏๆœฌๆœƒๅˆ†ๆ•ฃๅˆฐ่ท้›ขๆœ€้ ็š„็ฏ€้ปžไธŠ๏ผŒๅ› ็‚บ่ท้›ข้€šๅธธไปฃ่กจไธๅŒ็š„็ซ™้ปžใ€‚",
+                "cluster/sched/mem": "่จ˜ๆ†ถ้ซ”ๅ‰ฉ้ค˜",
+                "cluster/sched/memory": "ๅฏ็”จ่จ˜ๆ†ถ้ซ”",
+                "cluster/sched/memory/hint": "ๅ„ชๅ…ˆ้ธๆ“‡ๆœ‰ๅคš้ค˜่จ˜ๆ†ถ้ซ”็š„็ฏ€้ปž",
+                "cluster/sched/next": "ไธ‹ไธ€ๅ€‹ๅทฅไฝœๅฐ‡ๅœจ {0} ๅŸท่กŒ",
+                "cluster/sched/nojob": "ๆญคๅข้›†ไธญๆฒ’ๆœ‰้€™ๅ€‹ๅทฅไฝœ",
+                "cluster/sched/nonodes": "ๆฒ’ๆœ‰ๅฏ่ฉ•ๅˆ†็š„็ฏ€้ปž",
+                "cluster/sched/ping": "ๅพ€่ฟ”ๆ™‚้–“",
+                "cluster/sched/preferred": "ๅๅฅฝๅŠŸ่ƒฝ",
+                "cluster/sched/preferred/hint": "ๅ„ชๅ…ˆ้ธๆ“‡ๅ…ทๅ‚™ๅทฅไฝœๆ‰€่ฆๆฑ‚ไน‹้ธ็”จๅŠŸ่ƒฝ็š„็ฏ€้ปž",
+                "cluster/sched/queue": "ไฝ‡ๅˆ—้•ทๅบฆ",
+                "cluster/sched/queue/hint": "้ฟ้–‹ๅทฒ็ถ“ๅฟ™็ขŒ็š„็ฏ€้ปž",
+                "cluster/sched/reset": "้‚„ๅŽŸ้ ่จญๅ€ผ",
+                "cluster/sched/restored": "ๅทฒ้‚„ๅŽŸ้ ่จญๆฌŠ้‡",
+                "cluster/sched/save": "ๅ„ฒๅญ˜ๆฌŠ้‡",
+                "cluster/sched/saved": "ๆฌŠ้‡ๅทฒๅฅ—็”จๅˆฐๆ•ดๅ€‹ๅข้›†",
+                "cluster/sched/score": "ๅˆ†ๆ•ธ",
+                "cluster/sched/skipped": "็•ฅ้Ž",
+                "cluster/sched/title": "ๆŽ’็จ‹",
+                "cluster/sched/unmeasured": "ๅฐšๆœชๆธฌ้‡",
+                "cluster/settings/tagline": "่จญๅฎšๆญค็ฏ€้ปž๏ผŒไปฅๅŠๅข้›†ๅฆ‚ไฝ•ๅ„ฒๅญ˜ๆช”ๆกˆ่ˆ‡ๅŸท่กŒๅทฅไฝœ",
+                "cluster/settings/title": "ๅข้›†่จญๅฎš",
+                "cluster/state": "็‹€ๆ…‹",
+                "cluster/state/cancelled": "ๅทฒๅ–ๆถˆ",
+                "cluster/state/committed": "ๅทฒๅฏซๅ…ฅ",
+                "cluster/state/degraded": "ๆ•ˆ่ƒฝ้™ไฝŽ",
+                "cluster/state/done": "ๅฎŒๆˆ",
+                "cluster/state/draining": "ๆŽ’็ฉบไธญ",
+                "cluster/state/failed": "ๅคฑๆ•—",
+                "cluster/state/maintenance": "็ถญ่ญทไธญ",
+                "cluster/state/offline": "้›ข็ทš",
+                "cluster/state/online": "็ทšไธŠ",
+                "cluster/state/pending": "ๅพ…่™•็†",
+                "cluster/state/queued": "ๆŽ’้šŠไธญ",
+                "cluster/state/running": "ๅŸท่กŒไธญ",
+                "cluster/state/scheduled": "ๅทฒๆŽ’็จ‹",
+                "cluster/state/stale": "้ŽๆœŸ",
+                "cluster/state/succeeded": "ๆˆๅŠŸ",
+                "cluster/state/unknown": "ๆœช็Ÿฅ",
+                "cluster/state/verified": "ๅทฒ้ฉ—่ญ‰",
+                "cluster/state/writing": "ๅฏซๅ…ฅไธญ",
+                "cluster/storage": "ๅ„ฒๅญ˜็ฉบ้–“",
+                "cluster/testing": "ๆธฌ่ฉฆไธญ...",
+                "cluster/thisnode": "ๆญค็ฏ€้ปž",
+                "cluster/tokenhint": "ๅชๆœ‰่จญๅฎšไบ†ๅฐๅค–็ถฒๅ€็š„็ฏ€้ปžๆ‰่ƒฝ็”ข็”ŸๆฌŠๆ–ใ€‚ๅฐ‡ๆฌŠๆ–่ฒผๅˆฐๅฆไธ€ๅ€‹็ฏ€้ปž็š„ๅข้›†่จญๅฎšไธญใ€‚",
+                "cluster/tokenline": "ๆ–ผ {0} ๅˆฐๆœŸ ยท ๅทฒไฝฟ็”จ {1} ๆฌก",
+                "cluster/ttl/1h": "1 ๅฐๆ™‚",
+                "cluster/ttl/24h": "24 ๅฐๆ™‚",
+                "cluster/ttl/7d": "7 ๅคฉ",
+                "cluster/tunnel": "้€š้“",
+                "cluster/tunnelhere": "้€š้“้€ฃๅˆฐๆญค็ฏ€้ปž",
+                "cluster/tunnelvia": "็ถ“็”ฑ {0} ็š„้€š้“",
+                "cluster/unreachable": "็„กๆณ•้€ฃ็ทš",
+                "cluster/updated": "ๆ›ดๆ–ฐๆ–ผ {0}",
+                "cluster/uptime": "้‹ไฝœๆ™‚้–“",
+                "cluster/uptimevalue": "{0} ๅฐๆ™‚ {1} ๅˆ†",
+                "cluster/version": "็‰ˆๆœฌ",
+                "cluster/via": "็ถ“็”ฑ {0}",
+                "cluster/vol/add": "ๆไพ›ๆญค็ฏ€้ปžไธŠ็š„่ณ‡ๆ–™ๅคพ",
+                "cluster/vol/addbtn": "ๅŠ ๅ…ฅๅ„ฒๅญ˜ๅท",
+                "cluster/vol/added": "ๅทฒๅŠ ๅ…ฅๅ„ฒๅญ˜ๅท",
+                "cluster/vol/autoro": "่‡ชๅ‹•ๅฐ‡ๅฟซๆปฟ็š„ๅ„ฒๅญ˜ๅท่จญ็‚บๅ”ฏ่ฎ€",
+                "cluster/vol/autorohint": "ๅฅ—็”จๅˆฐๆฏๅ€‹็ฏ€้ปžใ€‚ๅ‰ฉ้ค˜็ฉบ้–“ไฝŽๆ–ผ 5% ็š„ๅ„ฒๅญ˜ๅทๆœƒๅœๆญขๆŽฅๆ”ถๆ–ฐๆช”ๆกˆ๏ผŒ้ซ˜ๆ–ผ 7% ๆ™‚ๅ†ๆขๅพฉใ€‚้—œ้–‰ๆญค้ …ๅฏ่ฎ“ๅ„ฒๅญ˜ๅท็”จๅˆฐๆปฟ๏ผ›ๅ…ˆๅ‰ๅ› ๆญค่ขซ้Ž–ๅฎš็š„ๅ„ฒๅญ˜ๅทๆœƒๅœจไธ€ๅˆ†้˜ๅ…งๆขๅพฉๅฏๅฏซๅ…ฅใ€‚",
+                "cluster/vol/autorooff": "ๅ„ฒๅญ˜ๅท็พๅœจๅฏไปฅๅฎŒๅ…จ็”จๆปฟ",
+                "cluster/vol/autoroon": "ๅฟซๆปฟ็š„ๅ„ฒๅญ˜ๅทๅฐ‡ๆœƒ่จญ็‚บๅ”ฏ่ฎ€",
+                "cluster/vol/confirmevac": "่ฆๅฐ‡ใ€Œ{0}ใ€ไธŠ็š„ๆ‰€ๆœ‰ๆช”ๆกˆ็งป่ตฐๅ—Ž๏ผŸๆญคๅ„ฒๅญ˜ๅทๆœƒ่ฎŠๆˆๅ”ฏ่ฎ€๏ผŒไธฆๅœจๆ‰€ๆœ‰ๅ‰ฏๆœฌ้ƒฝๅญ˜ๅœจๆ–ผๅˆฅ่™•ๅพŒ้€€ๅฝนใ€‚",
+                "cluster/vol/confirmremove": "่ฆๅœๆญขๆไพ›ใ€Œ{0}ใ€ๅ—Ž๏ผŸๆช”ๆกˆๆœƒ็•™ๅœจ็ฃ็ขŸไธŠ๏ผŒไฝ†ๅชๅญ˜ๅœจ้‚ฃ่ฃก็š„ๅ‰ฏๆœฌๅฐ‡็„กๆณ•ไฝฟ็”จใ€‚",
+                "cluster/vol/evacstarted": "ๅทฒ้–‹ๅง‹ๆ’ค้›ข๏ผš{0} ๅ€‹ๆช”ๆกˆๅพ…็งปๅ‹•",
+                "cluster/vol/evacuate": "ๆ’ค้›ข",
+                "cluster/vol/evacuating": "ๆ’ค้›ขไธญ",
+                "cluster/vol/files": "ๆช”ๆกˆ",
+                "cluster/vol/full": "ๅฟซๆปฟ",
+                "cluster/vol/hint": "ๅฏซๅ…ฅ cluster:/ ็š„ๆช”ๆกˆๆœƒไปฅไธ€่ˆฌๆช”ๆกˆๅญ˜ๆ”พๅœจๆไพ›็š„่ณ‡ๆ–™ๅคพไธญใ€‚่ณ‡ๆ–™ๅคพไธญๅŽŸๆœ‰็š„ๆช”ๆกˆๆœƒๅœจไธ‹ๆฌก้‡ๆ–ฐๆŽƒๆๆ™‚็ดๅ…ฅๅ‘ฝๅ็ฉบ้–“ใ€‚ๅข้›†่‡ณๅฐ‘ๆœ‰ไธ€ๅ€‹ๅ„ฒๅญ˜ๅทๅพŒ๏ผŒcluster:/ ็ฃ็ขŸๆ‰ๆœƒๅ‡บ็พๅœจๆช”ๆกˆ็ฎก็†ๅ™จไธญใ€‚",
+                "cluster/vol/none": "ๅฐš็„กๅ„ฒๅญ˜ๅทใ€‚ๅŠ ๅ…ฅๆญค็ฏ€้ปžไธŠ็š„่ณ‡ๆ–™ๅคพๅณๅฏ้–‹ๅง‹ๅœจ cluster:/ ไธญๅญ˜ๆ”พๆช”ๆกˆ",
+                "cluster/vol/readonly": "่จญ็‚บๅ”ฏ่ฎ€",
+                "cluster/vol/rescan": "้‡ๆ–ฐๆŽƒๆๆญค็ฏ€้ปž",
+                "cluster/vol/rescanned": "้‡ๆ–ฐๆŽƒๆๅฎŒๆˆ๏ผš็ดๅ…ฅ {0} ๅ€‹๏ผŒ้ŽๆœŸ {1} ๅ€‹",
+                "cluster/vol/ro": "ๅ”ฏ่ฎ€",
+                "cluster/vol/space": "ๅ‰ฉ้ค˜ / ็ธฝๅฎน้‡",
+                "cluster/vol/summary": "{0} ๅ€‹ๅ„ฒๅญ˜ๅท๏ผŒๅ‰ฉ้ค˜ {1}",
+                "cluster/vol/volume": "ๅ„ฒๅญ˜ๅท",
+                "cluster/vol/writable": "่จญ็‚บๅฏๅฏซๅ…ฅ",
+                "cluster/waitinghost": "ๆญฃๅœจ็ญ‰ๅพ…ๅฏ้€ฃ็ทš็š„ๆˆๅ“ก",
+                "cluster/when": "ๆ™‚้–“",
+                "cluster/willtunnel": "ๅŠ ๅ…ฅๅข้›†ๅพŒๅฐ‡้€้Žๆˆๅ“กๅปบ็ซ‹้€š้“",
+                "cluster/you": "ไฝ ",
+                "msg/AGI gateway not ready": "AGI ้–˜้“ๅฐšๆœชๅฐฑ็ท’",
+                "msg/Invalid MAC address": "MAC ไฝๅ€็„กๆ•ˆ",
+                "msg/POST required": "ๅฟ…้ ˆไฝฟ็”จ POST",
+                "msg/URL must start with http:// or https://": "็ถฒๅ€ๅฟ…้ ˆไปฅ http:// ๆˆ– https:// ้–‹้ ญ",
+                "msg/a job id is required": "ๅฟ…้ ˆๆไพ›ๅทฅไฝœ ID",
+                "msg/a map/reduce job needs a dataset pattern": "map/reduce ๅทฅไฝœ้œ€่ฆ่ณ‡ๆ–™้›†ๆจฃๅผ",
+                "msg/a map/reduce script must define mapper(files, emit) and reducer(key, values)": "map/reduce ่…ณๆœฌๅฟ…้ ˆๅฎš็พฉ mapper(files, emit) ่ˆ‡ reducer(key, values)",
+                "msg/advertise URL must start with http:// or https://": "ๅฐๅค–็ถฒๅ€ๅฟ…้ ˆไปฅ http:// ๆˆ– https:// ้–‹้ ญ",
+                "msg/args must be valid JSON": "ๅƒๆ•ธๅฟ…้ ˆๆ˜ฏๆœ‰ๆ•ˆ็š„ JSON",
+                "msg/cannot remove the namespace root": "็„กๆณ•็งป้™คๅ‘ฝๅ็ฉบ้–“็š„ๆ น็›ฎ้Œ„",
+                "msg/checksum mismatch": "ๆชขๆŸฅ็ขผไธ็ฌฆ",
+                "msg/chunk checksum mismatch": "ๅ€ๅกŠๆชขๆŸฅ็ขผไธ็ฌฆ",
+                "msg/chunk exceeds file size": "ๅ€ๅกŠ่ถ…้Žๆช”ๆกˆๅคงๅฐ",
+                "msg/chunk index out of range": "ๅ€ๅกŠ็ดขๅผ•่ถ…ๅ‡บ็ฏ„ๅœ",
+                "msg/chunk size invalid": "ๅ€ๅกŠๅคงๅฐ็„กๆ•ˆ",
+                "msg/cluster jobs not available": "ๅข้›†ๅทฅไฝœ็„กๆณ•ไฝฟ็”จ",
+                "msg/cluster name cannot be empty": "ๅข้›†ๅ็จฑไธๅฏ็‚บ็ฉบ",
+                "msg/cluster name required": "ๅฟ…้ ˆ่ผธๅ…ฅๅข้›†ๅ็จฑ",
+                "msg/directory not empty": "่ณ‡ๆ–™ๅคพไธๆ˜ฏ็ฉบ็š„",
+                "msg/disk usage not supported on this platform": "ๆญคๅนณๅฐไธๆ”ฏๆด็ฃ็ขŸไฝฟ็”จ้‡ๆŸฅ่ฉข",
+                "msg/drive required": "ๅฟ…้ ˆ้ธๆ“‡็ฃ็ขŸ",
+                "msg/empty response": "ๅ›žๆ‡‰ๆ˜ฏ็ฉบ็š„",
+                "msg/enabled must be true or false": "enabled ๅฟ…้ ˆๆ˜ฏ true ๆˆ– false",
+                "msg/event bus not available": "ไบ‹ไปถๅŒฏๆตๆŽ’็„กๆณ•ไฝฟ็”จ",
+                "msg/event data too large (64 KB max)": "ไบ‹ไปถ่ณ‡ๆ–™ๅคชๅคง๏ผˆไธŠ้™ 64 KB๏ผ‰",
+                "msg/event type required": "ๅฟ…้ ˆๆไพ›ไบ‹ไปถ้กžๅž‹",
+                "msg/evict notice is not for this node": "็งป้™ค้€š็Ÿฅไธๆ˜ฏ็ตฆๆญค็ฏ€้ปž็š„",
+                "msg/file changed since the copy was planned": "ๆช”ๆกˆๅœจ่ฆๅŠƒ่ค‡่ฃฝๅพŒๅทฒ่ฎŠๆ›ด",
+                "msg/file no longer exists in the namespace": "ๆช”ๆกˆๅทฒไธๅญ˜ๅœจๆ–ผๅ‘ฝๅ็ฉบ้–“ไธญ",
+                "msg/file not found on volume": "ๅ„ฒๅญ˜ๅทไธŠๆ‰พไธๅˆฐๆช”ๆกˆ",
+                "msg/folder path required": "ๅฟ…้ ˆ่ผธๅ…ฅ่ณ‡ๆ–™ๅคพ่ทฏๅพ‘",
+                "msg/folder required": "ๅฟ…้ ˆ่ผธๅ…ฅ่ณ‡ๆ–™ๅคพ",
+                "msg/hook not found": "ๆ‰พไธๅˆฐๆŽ›้‰ค",
+                "msg/insufficient cpu cores": "CPU ๆ ธๅฟƒไธ่ถณ",
+                "msg/insufficient memory": "่จ˜ๆ†ถ้ซ”ไธ่ถณ",
+                "msg/invalid append payload": "็„กๆ•ˆ็š„้™„ๅŠ ่ณ‡ๆ–™",
+                "msg/invalid batch": "็„กๆ•ˆ็š„ๆ‰นๆฌก",
+                "msg/invalid begin request": "็„กๆ•ˆ็š„้–‹ๅง‹่ฆๆฑ‚",
+                "msg/invalid entry": "็„กๆ•ˆ็š„้ …็›ฎ",
+                "msg/invalid heartbeat": "็„กๆ•ˆ็š„ๅฟƒ่ทณ",
+                "msg/invalid job spec": "็„กๆ•ˆ็š„ๅทฅไฝœ่จญๅฎš",
+                "msg/invalid join request": "็„กๆ•ˆ็š„ๅŠ ๅ…ฅ่ฆๆฑ‚",
+                "msg/invalid lease": "็„กๆ•ˆ็š„็งŸ็ด„",
+                "msg/invalid node URL": "็ฏ€้ปž็ถฒๅ€็„กๆ•ˆ",
+                "msg/invalid node public key": "็ฏ€้ปžๅ…ฌ้–‹้‡‘้‘ฐ็„กๆ•ˆ",
+                "msg/invalid node record": "็„กๆ•ˆ็š„็ฏ€้ปž็ด€้Œ„",
+                "msg/invalid range": "็„กๆ•ˆ็š„็ฏ„ๅœ",
+                "msg/invalid rename": "็„กๆ•ˆ็š„้‡ๆ–ฐๅ‘ฝๅ",
+                "msg/invalid report": "็„กๆ•ˆ็š„ๅ›žๅ ฑ",
+                "msg/invalid request": "็„กๆ•ˆ็š„่ฆๆฑ‚",
+                "msg/invalid sync payload": "็„กๆ•ˆ็š„ๅŒๆญฅ่ณ‡ๆ–™",
+                "msg/invalid task": "็„กๆ•ˆ็š„ๅทฅไฝœ",
+                "msg/invalid username": "ไฝฟ็”จ่€…ๅ็จฑ็„กๆ•ˆ",
+                "msg/is a directory": "้€™ๆ˜ฏ่ณ‡ๆ–™ๅคพ",
+                "msg/job cancelled": "ๅทฅไฝœๅทฒๅ–ๆถˆ",
+                "msg/job id required": "ๅฟ…้ ˆๆไพ›ๅทฅไฝœ ID",
+                "msg/job is limited to other nodes": "ๆญคๅทฅไฝœๅช่ƒฝๅœจๅ…ถไป–็ฏ€้ปžไธŠๅŸท่กŒ",
+                "msg/job not found": "ๆ‰พไธๅˆฐๅทฅไฝœ",
+                "msg/job owner is required": "ๅฟ…้ ˆๆไพ›ๅทฅไฝœๆ“ๆœ‰่€…",
+                "msg/job script is empty": "ๅทฅไฝœ่…ณๆœฌๆ˜ฏ็ฉบ็š„",
+                "msg/job timed out": "ๅทฅไฝœ้€พๆ™‚",
+                "msg/join response does not match the token's cluster": "ๅŠ ๅ…ฅๅ›žๆ‡‰่ˆ‡ๆฌŠๆ–ๆ‰€ๅฑฌ็š„ๅข้›†ไธ็ฌฆ",
+                "msg/join token has an invalid node URL": "ๅŠ ๅ…ฅๆฌŠๆ–ไธญ็š„็ฏ€้ปž็ถฒๅ€็„กๆ•ˆ",
+                "msg/join token is corrupted": "ๅŠ ๅ…ฅๆฌŠๆ–ๅทฒๆๆฏ€",
+                "msg/join token is for another cluster": "ๆญคๅŠ ๅ…ฅๆฌŠๆ–ๅฑฌๆ–ผๅฆไธ€ๅ€‹ๅข้›†",
+                "msg/join token is incomplete": "ๅŠ ๅ…ฅๆฌŠๆ–ไธๅฎŒๆ•ด",
+                "msg/join token is invalid or expired": "ๅŠ ๅ…ฅๆฌŠๆ–็„กๆ•ˆๆˆ–ๅทฒ้ŽๆœŸ",
+                "msg/join token required": "ๅฟ…้ ˆๆไพ›ๅŠ ๅ…ฅๆฌŠๆ–",
+                "msg/metadata store not available": "ไธญ็นผ่ณ‡ๆ–™ๅ„ฒๅญ˜ๅ€็„กๆณ•ไฝฟ็”จ",
+                "msg/no cluster volume available; add one in System Settings > Cluster Settings > Storage": "ๆฒ’ๆœ‰ๅฏ็”จ็š„ๅข้›†ๅ„ฒๅญ˜ๅท๏ผ›่ซ‹ๅœจใ€Œ็ณป็ตฑ่จญๅฎš > ๅข้›†่จญๅฎš > ๅ„ฒๅญ˜็ฉบ้–“ใ€ไธญๅŠ ๅ…ฅ",
+                "msg/no healthy copy of this file is reachable right now": "็›ฎๅ‰็„กๆณ•้€ฃ็ทšๅˆฐๆญคๆช”ๆกˆ็š„ไปปไฝ•ๅฅๅบทๅ‰ฏๆœฌ",
+                "msg/no node is available": "ๆฒ’ๆœ‰ๅฏ็”จ็š„็ฏ€้ปž",
+                "msg/node already has enough copies in flight": "ๆญค็ฏ€้ปžๅทฒๆœ‰่ถณๅค ็š„่ค‡่ฃฝๆญฃๅœจ้€ฒ่กŒ",
+                "msg/node id is required": "ๅฟ…้ ˆๆไพ›็ฏ€้ปž ID",
+                "msg/node id required": "ๅฟ…้ ˆๆไพ›็ฏ€้ปž ID",
+                "msg/node is DEGRADED": "็ฏ€้ปžๆ•ˆ่ƒฝ้™ไฝŽ",
+                "msg/node is DRAINING": "็ฏ€้ปžๆŽ’็ฉบไธญ",
+                "msg/node is MAINTENANCE": "็ฏ€้ปž็ถญ่ญทไธญ",
+                "msg/node is OFFLINE": "็ฏ€้ปžๅทฒ้›ข็ทš",
+                "msg/node is UNKNOWN": "็ฏ€้ปž็‹€ๆ…‹ไธๆ˜Ž",
+                "msg/node is degraded": "็ฏ€้ปžๆ•ˆ่ƒฝ้™ไฝŽ",
+                "msg/node is draining": "็ฏ€้ปžๆŽ’็ฉบไธญ",
+                "msg/node is maintenance": "็ฏ€้ปž็ถญ่ญทไธญ",
+                "msg/node is not available": "็ฏ€้ปž็„กๆณ•ไฝฟ็”จ",
+                "msg/node is offline": "็ฏ€้ปžๅทฒ้›ข็ทš",
+                "msg/node is unknown": "็ฏ€้ปž็‹€ๆ…‹ไธๆ˜Ž",
+                "msg/node lost while running the job": "ๅŸท่กŒๅทฅไฝœๆ™‚็ฏ€้ปžๅคฑๅŽป้€ฃ็ทš",
+                "msg/node name cannot be empty": "็ฏ€้ปžๅ็จฑไธๅฏ็‚บ็ฉบ",
+                "msg/node not found": "ๆ‰พไธๅˆฐ็ฏ€้ปž",
+                "msg/node refused this job earlier": "ๆญค็ฏ€้ปžๅ…ˆๅ‰ๆ‹’็ต•ไบ†้€™ๅ€‹ๅทฅไฝœ",
+                "msg/not a directory": "ไธๆ˜ฏ่ณ‡ๆ–™ๅคพ",
+                "msg/not a file": "ไธๆ˜ฏๆช”ๆกˆ",
+                "msg/not an ArozOS join token": "้€™ไธๆ˜ฏ ArozOS ๅŠ ๅ…ฅๆฌŠๆ–",
+                "msg/not in a cluster": "ๆœชๅŠ ๅ…ฅๅข้›†",
+                "msg/not logged in": "ๅฐšๆœช็™ปๅ…ฅ",
+                "msg/offset beyond end of file": "ไฝ็งป่ถ…ๅ‡บๆช”ๆกˆ็ตๅฐพ",
+                "msg/only the metadata leader assigns jobs": "ๅชๆœ‰ไธญ็นผ่ณ‡ๆ–™ไธปๅฐŽ็ฏ€้ปžๅฏไปฅๅˆ†้…ๅทฅไฝœ",
+                "msg/only the metadata leader assigns replication tasks": "ๅชๆœ‰ไธญ็นผ่ณ‡ๆ–™ไธปๅฐŽ็ฏ€้ปžๅฏไปฅๅˆ†้…่ค‡่ฃฝๅทฅไฝœ",
+                "msg/owner, script and at least one event type are required": "ๅฟ…้ ˆๆไพ›ๆ“ๆœ‰่€…ใ€่…ณๆœฌ่ˆ‡่‡ณๅฐ‘ไธ€็จฎไบ‹ไปถ้กžๅž‹",
+                "msg/path already exists": "่ทฏๅพ‘ๅทฒๅญ˜ๅœจ",
+                "msg/path escapes the volume": "่ทฏๅพ‘่ถ…ๅ‡บไบ†ๅ„ฒๅญ˜ๅท็ฏ„ๅœ",
+                "msg/path not found in the cluster namespace": "ๅœจๅข้›†ๅ‘ฝๅ็ฉบ้–“ไธญๆ‰พไธๅˆฐๆญค่ทฏๅพ‘",
+                "msg/path required": "ๅฟ…้ ˆ่ผธๅ…ฅ่ทฏๅพ‘",
+                "msg/peer has no reachable endpoint": "ๅฐๆ–น็ฏ€้ปžๆฒ’ๆœ‰ๅฏ้€ฃ็ทš็š„็ซฏ้ปž",
+                "msg/permission denied": "ๆฌŠ้™ไธ่ถณ",
+                "msg/previous node stopped reporting": "ๅ…ˆๅ‰็š„็ฏ€้ปžๅœๆญขๅ›žๅ ฑ",
+                "msg/refusing to delete the volume root": "ๆ‹’็ต•ๅˆช้™คๅ„ฒๅญ˜ๅทๆ น็›ฎ้Œ„",
+                "msg/refusing to drop the last healthy copy": "ๆ‹’็ต•ๅˆช้™คๆœ€ๅพŒไธ€ไปฝๅฅๅบทๅ‰ฏๆœฌ",
+                "msg/replicas must be between 0 and 16": "ๅ‰ฏๆœฌๆ•ธๅฟ…้ ˆไป‹ๆ–ผ 0 ๅˆฐ 16 ไน‹้–“",
+                "msg/replicas must be between 1 and 16": "ๅ‰ฏๆœฌๆ•ธๅฟ…้ ˆไป‹ๆ–ผ 1 ๅˆฐ 16 ไน‹้–“",
+                "msg/request is not signed": "่ฆๆฑ‚ๆœช็ฐฝ็ฝฒ",
+                "msg/request signature invalid": "่ฆๆฑ‚็š„็ฐฝ็ซ ็„กๆ•ˆ",
+                "msg/request targets another cluster": "่ฆๆฑ‚็š„็›ฎๆจ™ๆ˜ฏๅฆไธ€ๅ€‹ๅข้›†",
+                "msg/request timestamp outside the accepted window": "่ฆๆฑ‚็š„ๆ™‚้–“ๆˆณ่จ˜่ถ…ๅ‡บๅ…่จฑ็ฏ„ๅœ๏ผˆ่ซ‹ๆชขๆŸฅ็ฏ€้ปž็š„ๆ™‚้˜๏ผ‰",
+                "msg/requested log range was compacted, take a snapshot": "่ฆๆฑ‚็š„็ด€้Œ„็ฏ„ๅœๅทฒ่ขซๅฃ“็ธฎ๏ผŒ่ซ‹ๆ”น็”จๅฟซ็…ง",
+                "msg/script path required": "ๅฟ…้ ˆๆไพ›่…ณๆœฌ่ทฏๅพ‘",
+                "msg/sender is not a member of this cluster": "ๅ‚ณ้€่€…ไธๆ˜ฏๆญคๅข้›†็š„ๆˆๅ“ก",
+                "msg/shutting down": "ๆญฃๅœจ้—œ้–‰",
+                "msg/source copy is not readable": "ไพ†ๆบๅ‰ฏๆœฌ็„กๆณ•่ฎ€ๅ–",
+                "msg/target node is not connected through a tunnel on this node": "็›ฎๆจ™็ฏ€้ปžๆฒ’ๆœ‰้€้Žๆญค็ฏ€้ปž็š„้€š้“้€ฃ็ทš",
+                "msg/task is for another node": "ๆญคๅทฅไฝœๅฑฌๆ–ผๅฆไธ€ๅ€‹็ฏ€้ปž",
+                "msg/the URL answered but it is not an ArozOS cluster endpoint": "ๆญค็ถฒๅ€ๆœ‰ๅ›žๆ‡‰๏ผŒไฝ†ไธๆ˜ฏ ArozOS ๅข้›†็ซฏ้ปž",
+                "msg/the job has no dataset pattern": "ๆญคๅทฅไฝœๆฒ’ๆœ‰่ณ‡ๆ–™้›†ๆจฃๅผ",
+                "msg/the job owner has no account on this node": "ๆญค็ฏ€้ปžไธŠๆฒ’ๆœ‰ๅทฅไฝœๆ“ๆœ‰่€…็š„ๅธณ่™Ÿ",
+                "msg/the mappers emitted more than 8 MB; narrow the dataset or emit fewer values": "mapper ่ผธๅ‡บ่ถ…้Ž 8 MB๏ผ›่ซ‹็ธฎๅฐ่ณ‡ๆ–™้›†ๆˆ–ๆธ›ๅฐ‘่ผธๅ‡บ็š„ๅ€ผ",
+                "msg/the user root itself cannot be a cluster volume; pick a sub folder such as /cluster": "ไฝฟ็”จ่€…ๆ น็›ฎ้Œ„ๆœฌ่บซไธ่ƒฝไฝœ็‚บๅข้›†ๅ„ฒๅญ˜ๅท๏ผ›่ซ‹้ธๆ“‡ๅญ่ณ‡ๆ–™ๅคพ๏ผŒไพ‹ๅฆ‚ /cluster",
+                "msg/this folder is already a cluster volume": "ๆญค่ณ‡ๆ–™ๅคพๅทฒ็ถ“ๆ˜ฏๅข้›†ๅ„ฒๅญ˜ๅท",
+                "msg/this job script defines no run(job) function": "ๆญคๅทฅไฝœ่…ณๆœฌๆฒ’ๆœ‰ๅฎš็พฉ run(job) ๅ‡ฝๅผ",
+                "msg/this node cannot execute jobs": "ๆญค็ฏ€้ปž็„กๆณ•ๅŸท่กŒๅทฅไฝœ",
+                "msg/this node has no advertised URL; generate the join token on a node that other nodes can reach": "ๆญค็ฏ€้ปžๆฒ’ๆœ‰ๅฐๅค–็ถฒๅ€๏ผ›่ซ‹ๅœจๅ…ถไป–็ฏ€้ปžๅฏไปฅ้€ฃ็ทš็š„็ฏ€้ปžไธŠ็”ข็”ŸๅŠ ๅ…ฅๆฌŠๆ–",
+                "msg/this node is already part of a cluster, leave it first": "ๆญค็ฏ€้ปžๅทฒๅŠ ๅ…ฅๅข้›†๏ผŒ่ซ‹ๅ…ˆ้›ข้–‹",
+                "msg/this node is not part of a cluster": "ๆญค็ฏ€้ปžๅฐšๆœชๅŠ ๅ…ฅๅข้›†",
+                "msg/this node is not the identity origin of the cluster": "ๆญค็ฏ€้ปžไธๆ˜ฏๅข้›†็š„่บซๅˆ†ไพ†ๆบ",
+                "msg/this node is not the metadata leader": "ๆญค็ฏ€้ปžไธๆ˜ฏไธญ็นผ่ณ‡ๆ–™็š„ไธปๅฐŽ็ฏ€้ปž",
+                "msg/timed out waiting for the job to finish": "็ญ‰ๅพ…ๅทฅไฝœๅฎŒๆˆ้€พๆ™‚",
+                "msg/token id required": "ๅฟ…้ ˆๆไพ›ๆฌŠๆ– ID",
+                "msg/token not found": "ๆ‰พไธๅˆฐๆฌŠๆ–",
+                "msg/tunnel closed": "้€š้“ๅทฒ้—œ้–‰",
+                "msg/unknown upload session": "ๆœช็Ÿฅ็š„ไธŠๅ‚ณๅทฅไฝœ้šŽๆฎต",
+                "msg/unreachable": "็„กๆณ•้€ฃ็ทš",
+                "msg/unsupported node state": "ไธๆ”ฏๆด็š„็ฏ€้ปž็‹€ๆ…‹",
+                "msg/upload incomplete": "ไธŠๅ‚ณๆœชๅฎŒๆˆ",
+                "msg/url required": "ๅฟ…้ ˆ่ผธๅ…ฅ็ถฒๅ€",
+                "msg/use leave to remove this node": "่ฆ็งป้™คๆญค็ฏ€้ปž่ซ‹ไฝฟ็”จใ€Œ้›ข้–‹ๅข้›†ใ€",
+                "msg/user assertion expired": "ไฝฟ็”จ่€…่ฒๆ˜Žๅทฒ้ŽๆœŸ",
+                "msg/user assertion invalid": "ไฝฟ็”จ่€…่ฒๆ˜Ž็„กๆ•ˆ",
+                "msg/user not found on the identity origin": "่บซๅˆ†ไพ†ๆบไธŠๆ‰พไธๅˆฐๆญคไฝฟ็”จ่€…",
+                "msg/volume id required": "ๅฟ…้ ˆๆไพ›ๅ„ฒๅญ˜ๅท ID",
+                "msg/volume is not on this node": "ๅ„ฒๅญ˜ๅทไธๅœจๆญค็ฏ€้ปžไธŠ",
+                "msg/volume not found": "ๆ‰พไธๅˆฐๅ„ฒๅญ˜ๅท",
+                "msg/volume not found on this node": "ๆญค็ฏ€้ปžไธŠๆ‰พไธๅˆฐๅ„ฒๅญ˜ๅท",
+                "msgp/architecture {0} not allowed": "ไธๅ…่จฑ็š„ๆžถๆง‹ {0}",
+                "msgp/cancelled by {0}": "็”ฑ {0} ๅ–ๆถˆ",
+                "msgp/could not assemble the reduce input: {0}": "็„กๆณ•็ต„ๅˆ reduce ็š„่ผธๅ…ฅ๏ผš{0}",
+                "msgp/could not create a map task: {0}": "็„กๆณ•ๅปบ็ซ‹ map ๅญๅทฅไฝœ๏ผš{0}",
+                "msgp/could not create the reduce task: {0}": "็„กๆณ•ๅปบ็ซ‹ reduce ๅญๅทฅไฝœ๏ผš{0}",
+                "msgp/drive {0} is not a local drive on this node": "็ฃ็ขŸ {0} ไธๆ˜ฏๆญค็ฏ€้ปžไธŠ็š„ๆœฌๆฉŸ็ฃ็ขŸ",
+                "msgp/hook script not found: {0}": "ๆ‰พไธๅˆฐๆŽ›้‰ค่…ณๆœฌ๏ผš{0}",
+                "msgp/invalid join response from {0}": "{0} ็š„ๅŠ ๅ…ฅๅ›žๆ‡‰็„กๆ•ˆ",
+                "msgp/invalid node key encoding: {0}": "็ฏ€้ปž้‡‘้‘ฐ็ทจ็ขผ็„กๆ•ˆ๏ผš{0}",
+                "msgp/invalid public key encoding: {0}": "ๅ…ฌ้–‹้‡‘้‘ฐ็ทจ็ขผ็„กๆ•ˆ๏ผš{0}",
+                "msgp/job timed out after {0}s": "ๅทฅไฝœๅœจ {0} ็ง’ๅพŒ้€พๆ™‚",
+                "msgp/local drive {0} is not mounted": "ๆœฌๆฉŸ็ฃ็ขŸ {0} ๆœชๆŽ›่ผ‰",
+                "msgp/missing feature {0}": "็ผบๅฐ‘ๅŠŸ่ƒฝ {0}",
+                "msgp/no cluster volume available; add one in System Settings > Cluster Settings > Storage ({0})": "ๆฒ’ๆœ‰ๅฏ็”จ็š„ๅข้›†ๅ„ฒๅญ˜ๅท๏ผ›่ซ‹ๅœจใ€Œ็ณป็ตฑ่จญๅฎš > ๅข้›†่จญๅฎš > ๅ„ฒๅญ˜็ฉบ้–“ใ€ไธญๅŠ ๅ…ฅ๏ผˆ{0}๏ผ‰",
+                "msgp/no reachable copy of: {0}": "็„กๆณ•้€ฃ็ทšๅˆฐไธ‹ๅˆ—ๆช”ๆกˆ็š„ๅ‰ฏๆœฌ๏ผš{0}",
+                "msgp/node {0} could not take the job: {1}": "็ฏ€้ปž {0} ็„กๆณ•ๆŽฅๆ”ถๆญคๅทฅไฝœ๏ผš{1}",
+                "msgp/os {0} not allowed": "ไธๅ…่จฑ็š„ไฝœๆฅญ็ณป็ตฑ {0}",
+                "msgp/permission denied: {0}": "ๆฌŠ้™ไธ่ถณ๏ผš{0}",
+                "msgp/remote node returned {0}: {1}": "้ ็ซฏ็ฏ€้ปžๅ›žๅ‚ณ {0}๏ผš{1}",
+                "msgp/script not found: {0}": "ๆ‰พไธๅˆฐ่…ณๆœฌ๏ผš{0}",
+                "msgp/this volume holds the only copy of {0} file(s); evacuate it first": "ๆญคๅ„ฒๅญ˜ๅทๆŒๆœ‰ {0} ๅ€‹ๆช”ๆกˆ็š„ๅ”ฏไธ€ๅ‰ฏๆœฌ๏ผ›่ซ‹ๅ…ˆๆ’ค้›ข",
+                "msgp/unable to reach {0}: {1}": "็„กๆณ•้€ฃ็ทšๅˆฐ {0}๏ผš{1}",
+                "msgp/unreachable: {0}": "็„กๆณ•้€ฃ็ทš๏ผš{0}",
+                "msgp/unsupported node URL scheme: {0}": "ไธๆ”ฏๆด็š„็ฏ€้ปž็ถฒๅ€ๅ”ๅฎš๏ผš{0}",
+                "msgp/weight {0} must be a number": "ๆฌŠ้‡ {0} ๅฟ…้ ˆๆ˜ฏๆ•ธๅญ—",
+                "msgp/weight {0} must be between 0 and 1": "ๆฌŠ้‡ {0} ๅฟ…้ ˆไป‹ๆ–ผ 0 ่ˆ‡ 1 ไน‹้–“",
+                "msgp/{0} produced an unreadable result": "{0} ็”ข็”Ÿไบ†็„กๆณ•่ฎ€ๅ–็š„็ตๆžœ"
+            },
+            "placeholder": {
+                "Name (optional)": "ๅ็จฑ๏ผˆ้ธๅกซ๏ผ‰",
+                "job id": "ๅทฅไฝœ ID",
+                "Nightly index": "ๆฏๆ™š็ดขๅผ•",
+                "50 per task": "ๆฏๅ€‹ๅญๅทฅไฝœ 50 ๅ€‹"
+            },
+            "titles": {
+                "Re-detect capabilities": "้‡ๆ–ฐๅตๆธฌๅŠŸ่ƒฝ"
+            }
+        },
+        "zh-cn": {
+            "name": "็ฎ€ไฝ“ไธญๆ–‡",
+            "fontFamily": "\"Microsoft YaHei\",\"SimHei\", \"PingFangSC-Medium\", \"STHeiti\"",
+            "strings": {
+                "cluster/ago/d": "{0} ๅคฉๅ‰",
+                "cluster/ago/h": "{0} ๅฐๆ—ถๅ‰",
+                "cluster/ago/m": "{0} ๅˆ†้’Ÿๅ‰",
+                "cluster/ago/s": "{0} ็ง’ๅ‰",
+                "cluster/auto": "่‡ชๅŠจ๏ผˆไปปไธ€ๅฏ่ฟžๆŽฅ็š„ๆˆๅ‘˜๏ผ‰",
+                "cluster/cancel": "ๅ–ๆถˆ",
+                "cluster/caps": "ๅŠŸ่ƒฝ",
+                "cluster/cfg/insecure": "ๆŽฅๅ—ๅ…ถไป–่Š‚็‚น็š„่‡ช็ญพๅ่ฏไนฆ๏ผˆไป…้™ๅฑ€ๅŸŸ็ฝ‘๏ผ‰",
+                "cluster/cfg/name": "่Š‚็‚นๅ็งฐ",
+                "cluster/cfg/test": "ๆต‹่ฏ•",
+                "cluster/cfg/tunnel": "้€š่ฟ‡้šง้“่ฟžๆŽฅ",
+                "cluster/cfg/url": "ๅฏนๅค–็ฝ‘ๅ€๏ผˆๅ…ถไป–่Š‚็‚น่ฟžๆŽฅๅˆฐๆญค่Š‚็‚น็š„ๆ–นๅผ๏ผ‰",
+                "cluster/cfg/urlhint": "ๅฆ‚ๆžœๆญค่Š‚็‚นๆฒกๆœ‰ๅ…ฌ็ฝ‘ๅœฐๅ€่ฏท็•™็ฉบใ€‚ๅฎƒไผšๆ”นไธบไธŽไธ€ไธชๅฏ่ฟžๆŽฅ็š„ๆˆๅ‘˜ไฟๆŒ้šง้“่ฟžๆŽฅ๏ผŒ่ฟ™ไนŸ่ƒฝ็ฉฟ่ฟ‡ Cloudflareใ€‚",
+                "cluster/clusterid": "้›†็พค ID",
+                "cluster/confirmleave": "่ฆ็ฆปๅผ€้›†็พคๅ—๏ผŸไน‹ๅŽๅฐ†ๆ— ๆณ•ไปŽ่ฟ™้‡Œ่ฎฟ้—ฎๅ…ถไป–่Š‚็‚นไธŠ็š„ๆ–‡ไปถๅ’ŒไปปๅŠกใ€‚",
+                "cluster/confirmremove": "่ฆ็งป้™ค่Š‚็‚นโ€œ{0}โ€ๅ—๏ผŸ",
+                "cluster/copied": "ๅทฒๅฐ†ไปค็‰Œๅคๅˆถๅˆฐๅ‰ช่ดดๆฟ",
+                "cluster/copy": "ๅคๅˆถ",
+                "cluster/coresvalue": "{0} ๆ ธ",
+                "cluster/create": "ๅˆ›ๅปบๆ–ฐ้›†็พค",
+                "cluster/createbtn": "ๅˆ›ๅปบ",
+                "cluster/created": "ๅˆ›ๅปบๆ—ถ้—ด",
+                "cluster/createhint": "ๆญค่Š‚็‚นๅฐ†ๆˆไธบ็ฌฌไธ€ไธชๆˆๅ‘˜ใ€‚ไน‹ๅŽๅœจ่ฟ™้‡Œ็”ŸๆˆๅŠ ๅ…ฅไปค็‰ŒไปฅๆทปๅŠ ๆ›ดๅคš่Š‚็‚นใ€‚",
+                "cluster/direct": "็›ด่ฟž",
+                "cluster/disk": "็ฃ็›˜",
+                "cluster/diskfree": "ๅ‰ฉไฝ™ {0}๏ผŒๅ…ฑ {1}",
+                "cluster/drain": "ๆŽ’็ฉบ",
+                "cluster/entername": "่ฏท่พ“ๅ…ฅ้›†็พคๅ็งฐ",
+                "cluster/entertoken": "่ฏท็ฒ˜่ดดๅŠ ๅ…ฅไปค็‰Œ",
+                "cluster/enterurl": "่ฏท่พ“ๅ…ฅ่ฆๆต‹่ฏ•็š„็ฝ‘ๅ€",
+                "cluster/expiresat": "ไบŽ {0} ่ฟ‡ๆœŸ",
+                "cluster/folder": "ๆ–‡ไปถๅคน",
+                "cluster/freeamount": "ๅ‰ฉไฝ™ {0}",
+                "cluster/gentoken": "็”ŸๆˆๅŠ ๅ…ฅไปค็‰Œ",
+                "cluster/id/accounts": "่ดฆๆˆท",
+                "cluster/id/apply": "ๅบ”็”จ",
+                "cluster/id/conflicts": "ไป…ๆœฌๅœฐ",
+                "cluster/id/conflictswhy": "ๅŒๅ็š„ๆœฌๅœฐ่ดฆๆˆทไผšไฟๆŒไธๅ˜",
+                "cluster/id/disabled": "ๅœ็”จ๏ผˆๆฏไธช่Š‚็‚นๅ„่‡ช้ชŒ่ฏ่‡ชๅทฑ็š„่ดฆๆˆท๏ผ‰",
+                "cluster/id/fallback": "ๆ— ๆณ•่ฟžๆŽฅๅˆฐ่บซไปฝๆบ๏ผŒๅทฒๆ”น็”จๆœฌๅœฐ่ดฆๆˆท",
+                "cluster/id/forward": "่ฝฌๅ‘",
+                "cluster/id/hint": "่บซไปฝๆบไผš้ชŒ่ฏ้›†็พคไธญ็š„ๆฏไธ€ๆฌก็™ปๅฝ•ใ€‚่ดฆๆˆทไผšๅคๅˆถๅˆฐๅ…ถไป–่Š‚็‚น๏ผŒๅ› ๆญคๅœจ่บซไปฝๆบๆ— ๆณ•่ฟžๆŽฅๆ—ถไปๅฏไฝฟ็”จใ€‚็”จๆˆท็ป„ๆŒ‰ๅ็งฐๅฏนๅบ”๏ผŒ่ฏทๅœจๆฏไธช่Š‚็‚นไธŠๅˆ›ๅปบ็›ธๅŒ็š„ๆƒ้™็ป„ใ€‚",
+                "cluster/id/lastlogin": "ไธŠๆฌก็™ปๅฝ•ๆฃ€ๆŸฅ",
+                "cluster/id/lastsync": "ไธŠๆฌกๅŒๆญฅ",
+                "cluster/id/local": "ๆฏไธช่Š‚็‚นไปฅ่‡ชๅทฑ็š„่ดฆๆˆท้ชŒ่ฏ็™ปๅฝ•",
+                "cluster/id/mirrors": "{1} ไธชๆœฌๅœฐ่ดฆๆˆทไธญๆœ‰ {0} ไธชไธŽ่บซไปฝๆบๅŒๆญฅ",
+                "cluster/id/mode": "ๆจกๅผ",
+                "cluster/id/origin": "ๆบ",
+                "cluster/id/originhere": "ๆญค่Š‚็‚น่ดŸ่ดฃ้ชŒ่ฏๆ•ดไธช้›†็พค็š„็™ปๅฝ•",
+                "cluster/id/published": "ๅทฒๅ‘ๅธƒ {0} ไธช็ป™ๆˆๅ‘˜",
+                "cluster/id/replicated": "ๅทฒๅคๅˆถ",
+                "cluster/id/skipped": "ๅทฒ่ทณ่ฟ‡",
+                "cluster/id/skippedwhy": "ๆญค่Š‚็‚นไธŠๆฒกๆœ‰ไป–ไปฌๆ‰€ๅฑž็š„ไปปไฝ•็”จๆˆท็ป„",
+                "cluster/id/sync": "็ซ‹ๅณๅŒๆญฅ",
+                "cluster/id/synced": "่ดฆๆˆทๅทฒๅŒๆญฅ",
+                "cluster/id/title": "่บซไปฝ๏ผˆๅ•็‚น็™ปๅฝ•๏ผ‰",
+                "cluster/id/verifiedby": "็™ปๅฝ•็”ฑ {0} ้ชŒ่ฏ",
+                "cluster/info/novolume": "้›†็พคๅฐšๆœช่ฎพ็ฝฎๅญ˜ๅ‚จ็ฉบ้—ดใ€‚",
+                "cluster/info/novolumehint": "ๅœจ้›†็พค่ฎพ็ฝฎไธญๆทปๅŠ ๅญ˜ๅ‚จๅท๏ผŒcluster:/ ็ฃ็›˜ๆ‰ไผšๅ‡บ็Žฐๅœจๆ–‡ไปถ็ฎก็†ๅ™จไธญใ€‚",
+                "cluster/info/standalone": "ๆญค่Š‚็‚นๅฐšๆœชๅŠ ๅ…ฅ้›†็พคใ€‚",
+                "cluster/info/standalonehint": "่ฏทๅœจ้›†็พค่ฎพ็ฝฎไธญๅˆ›ๅปบๆˆ–ๅŠ ๅ…ฅ้›†็พคใ€‚ๅœจๆญคไน‹ๅ‰ cluster:/ ็ฃ็›˜ไผšไฟๆŒ้š่—ใ€‚",
+                "cluster/info/tagline": "ๆญค่Š‚็‚นใ€ๅ…ถไป–่Š‚็‚นใ€ๅ‘ฝๅ็ฉบ้—ดไธŽ่ฐƒๅบฆๅ™จ็š„็Šถๆ€",
+                "cluster/info/title": "้›†็พคไฟกๆฏ",
+                "cluster/invite": "ๆทปๅŠ ่Š‚็‚น",
+                "cluster/jobs/args": "ๅ‚ๆ•ฐ๏ผˆJSON๏ผŒๅฏ้€‰๏ผ‰",
+                "cluster/jobs/attemptn": "็ฌฌ {0} ๆฌกๅฐ่ฏ•",
+                "cluster/jobs/browse": "ๆต่งˆ",
+                "cluster/jobs/cancel": "ๅ–ๆถˆ",
+                "cluster/jobs/complete": "ๅทฒๅฎŒๆˆ {0} / {1}",
+                "cluster/jobs/dataset": "ๆ•ฐๆฎ้›†",
+                "cluster/jobs/datasethint": "ไฝฟ็”จๆ•ฐๆฎ้›†ๆ—ถ๏ผŒ่„šๆœฌๅฟ…้กปๅฎšไน‰ mapper(files, emit) ๅ’Œ reducer(key, values)๏ผ›ๆ–‡ไปถไผšๅœจๆŒๆœ‰ๅฎƒไปฌ็š„่Š‚็‚นไธŠๅค„็†ใ€‚",
+                "cluster/jobs/datasetlabel": "Map/reduce ๆ•ฐๆฎ้›†๏ผˆๅฏ้€‰็š„้€š้…็ฌฆ๏ผ›ไผšๅฐ†ๆญคไปปๅŠกๅ˜ไธบ map/reduce ไปปๅŠก๏ผ‰",
+                "cluster/jobs/detail": "ไปปๅŠก่ฏฆๆƒ…",
+                "cluster/jobs/duration": "่€—ๆ—ถ",
+                "cluster/jobs/failedn": "{0} ไธชๅคฑ่ดฅ",
+                "cluster/jobs/features": "ๅฟ…้œ€ๅŠŸ่ƒฝ",
+                "cluster/jobs/inputs": "่พ“ๅ…ฅ่ทฏๅพ„๏ผˆไปฅ้€—ๅทๅˆ†้š”๏ผŒ็”จไบŽๅœจๆ•ฐๆฎ้™„่ฟ‘่ฟ่กŒ๏ผ‰",
+                "cluster/jobs/job": "ไปปๅŠก",
+                "cluster/jobs/jobs": "ไปปๅŠก",
+                "cluster/jobs/kind/map": "map ๅญไปปๅŠก",
+                "cluster/jobs/kind/mapreduce": "map/reduce",
+                "cluster/jobs/kind/reduce": "reduce ๅญไปปๅŠก",
+                "cluster/jobs/log": "ๆ—ฅๅฟ—",
+                "cluster/jobs/minsecs": "{0} ๅˆ† {1} ็ง’",
+                "cluster/jobs/name": "ๅ็งฐ",
+                "cluster/jobs/needscript": "ๅฟ…้กปๆŒ‡ๅฎš่„šๆœฌ่ทฏๅพ„",
+                "cluster/jobs/node": "่Š‚็‚น",
+                "cluster/jobs/nodetail": "้€‰ๆ‹ฉไธ€ไธชไปปๅŠกไปฅๆŸฅ็œ‹ๅ…ถๆ—ฅๅฟ—ๅ’Œ่พ“ๅ‡บ",
+                "cluster/jobs/none": "ๅฐšๆ— ไปปๅŠก",
+                "cluster/jobs/output": "่พ“ๅ‡บ",
+                "cluster/jobs/owner": "ๆ‰€ๆœ‰่€…",
+                "cluster/jobs/progress": "่ฟ›ๅบฆ",
+                "cluster/jobs/queued": "ไปปๅŠกๅทฒๅŠ ๅ…ฅ้˜Ÿๅˆ—",
+                "cluster/jobs/run": "่ฟ่กŒไปปๅŠก",
+                "cluster/jobs/scheduler": "่ฐƒๅบฆๅ™จ๏ผš{0}",
+                "cluster/jobs/script": "่„šๆœฌ",
+                "cluster/jobs/scriptlabel": "่„šๆœฌ๏ผˆๅฎšไน‰ run(job) ็š„ .agi ๆ–‡ไปถ๏ผ‰",
+                "cluster/jobs/secs": "{0} ็ง’",
+                "cluster/jobs/slots": "ๆญค่Š‚็‚น {0}/{1} ไธชๆ‰ง่กŒๆงฝ็นๅฟ™",
+                "cluster/jobs/status": "็Šถๆ€",
+                "cluster/jobs/submit": "ๆไบคไปปๅŠก",
+                "cluster/jobs/submithint": "่„šๆœฌไผšไปฅไฝ ็š„ๆƒ้™ๅœจ้€‰ๅฎš็š„่Š‚็‚นไธŠ่ฟ่กŒใ€‚cluster:/ ่ทฏๅพ„็…งๅธธๅฏ็”จใ€‚",
+                "cluster/jobs/summary": "{0} ไธช่ฟ่กŒไธญ๏ผŒ{1} ไธชๆŽ’้˜Ÿไธญ๏ผŒ{2} ไธชๅทฒๅฎŒๆˆ",
+                "cluster/jobs/tagline": "ๅœจๆœ€ๅˆ้€‚็š„่Š‚็‚นไธŠ่ฟ่กŒ่„šๆœฌ",
+                "cluster/jobs/tasks": "ๅญไปปๅŠก",
+                "cluster/jobs/timeout": "่ถ…ๆ—ถ๏ผˆ็ง’๏ผ‰",
+                "cluster/jobs/title": "้›†็พคไปปๅŠก",
+                "cluster/jobs/typepath": "่ฏท่พ“ๅ…ฅ่„šๆœฌ่ทฏๅพ„๏ผŒไพ‹ๅฆ‚ user:/jobs/index.agi",
+                "cluster/jobs/waiting": "็ญ‰ๅพ…ๅŽŸๅ› ",
+                "cluster/join": "ๅŠ ๅ…ฅ็Žฐๆœ‰้›†็พค",
+                "cluster/joinbtn": "ๅŠ ๅ…ฅ",
+                "cluster/joinhint": "็ฒ˜่ดดๅœจ้›†็พคไธญๅฏ่ฟžๆŽฅๆˆๅ‘˜ไธŠ็”Ÿๆˆ็š„ๅŠ ๅ…ฅไปค็‰Œใ€‚",
+                "cluster/joining": "ๆญฃๅœจๅŠ ๅ…ฅ...",
+                "cluster/justnow": "ๅˆšๅˆš",
+                "cluster/key": "่Š‚็‚นๅฏ†้’ฅ",
+                "cluster/lastseen": "ๆœ€ๅŽ่ฟžๆŽฅ",
+                "cluster/leave": "็ฆปๅผ€้›†็พค",
+                "cluster/link/info": "้›†็พคไฟกๆฏ",
+                "cluster/link/jobs": "้›†็พคไปปๅŠก",
+                "cluster/link/settings": "้›†็พค่ฎพ็ฝฎ",
+                "cluster/load": "่ดŸ่ฝฝ",
+                "cluster/maintenance": "็ปดๆŠค",
+                "cluster/members": "ๆˆๅ‘˜",
+                "cluster/membershint": "็ปดๆŠคๆจกๅผไผš่ฎฉ่Š‚็‚น้€€ๅ‡บ่ฐƒๅบฆใ€‚ๆŽ’็ฉบ่ฟ˜ไผšๆŠŠๅฎƒ็š„ๅ‰ฏๆœฌ็งปๅˆฐๅ…ถไป–่Š‚็‚น๏ผŒไน‹ๅŽๅณๅฏๅฎ‰ๅ…จๅ…ณๆœบใ€‚",
+                "cluster/meta/entries": "ๆก็›ฎ",
+                "cluster/meta/entriesvalue": "{0} ไธชๆ–‡ไปถใ€{1} ไธชๆ–‡ไปถๅคนใ€{2} ไธชๅญ˜ๅ‚จๅท",
+                "cluster/meta/leader": "ไธป่Š‚็‚น",
+                "cluster/meta/log": "ๆ—ฅๅฟ—",
+                "cluster/meta/noleader": "้€‰ไธพไธญ",
+                "cluster/meta/pending": "{0} ๆกๅพ…ๅค„็†",
+                "cluster/meta/term": "ไปปๆœŸ {0}",
+                "cluster/natonly": "ๆ— ็ฝ‘ๅ€",
+                "cluster/never": "ไปŽๆœช",
+                "cluster/node": "่Š‚็‚น",
+                "cluster/nodes": "่Š‚็‚น",
+                "cluster/nonodes": "ๆฒกๆœ‰่Š‚็‚น",
+                "cluster/noreport": "ๅฐšๆ— ๆŠฅๅ‘Š",
+                "cluster/noroute": "ๆ— ่ทฏๅพ„",
+                "cluster/notmember": "ๅฐšๆœชๅŠ ๅ…ฅ้›†็พค",
+                "cluster/notunnel": "ๆ— ้šง้“",
+                "cluster/now": "็Žฐๅœจ",
+                "cluster/platform": "ๅนณๅฐ",
+                "cluster/platformline": "{0} / {1}๏ผŒ{2} ๆ ธ",
+                "cluster/pol/1": "1 ไปฝๅ‰ฏๆœฌ",
+                "cluster/pol/2": "2 ไปฝๅ‰ฏๆœฌ",
+                "cluster/pol/3": "3 ไปฝๅ‰ฏๆœฌ",
+                "cluster/pol/4": "4 ไปฝๅ‰ฏๆœฌ",
+                "cluster/pol/copies": "{0} ไปฝๅ‰ฏๆœฌ",
+                "cluster/pol/hint": "ๆฒกๆœ‰่ฎพ็ฝฎ็ญ–็•ฅ็š„ๆ–‡ไปถๅคนๅชไฟ็•™ไธ€ไปฝๅ‰ฏๆœฌใ€‚้ขๅค–็š„ๅ‰ฏๆœฌ็”ฑๅคๅˆถๆœๅŠกๅˆ›ๅปบใ€‚",
+                "cluster/pol/label": "ๆฏไธช้กถๅฑ‚ๆ–‡ไปถๅคน็š„ๅ‰ฏๆœฌๆ•ฐ",
+                "cluster/pol/set": "่ฎพ็ฝฎ",
+                "cluster/pol/title": "ๅ‰ฏๆœฌ็ญ–็•ฅ",
+                "cluster/probe": "ๆŽขๆต‹",
+                "cluster/probefail": "{0}๏ผš{1}๏ผˆ{2}๏ผ‰",
+                "cluster/probeok": "{0}๏ผš{1} ๆฏซ็ง’๏ผˆ{2}๏ผ‰",
+                "cluster/reach": "่ฟžๆŽฅๆ–นๅผ",
+                "cluster/reachableok": "ๅฏ่ฟžๆŽฅ๏ผŒๅพ€่ฟ” {0} ๆฏซ็ง’",
+                "cluster/redetect": "้‡ๆ–ฐๆฃ€ๆต‹",
+                "cluster/remove": "็งป้™ค",
+                "cluster/repl/evacuating": "{0} ไธชๅญ˜ๅ‚จๅทๆ’ค็ฆปไธญ",
+                "cluster/repl/files": "ๆ–‡ไปถ",
+                "cluster/repl/fromto": "{0} ๅˆฐ {1}",
+                "cluster/repl/healthy": "ๆ‰€ๆœ‰ๆ–‡ไปถๅ‡็ฌฆๅˆ็ญ–็•ฅ",
+                "cluster/repl/inflight": "่ฟ›่กŒไธญ",
+                "cluster/repl/inflightvalue": "{0} ไธช่ฟ่กŒไธญ๏ผŒ{1} ไธชๆŽ’้˜Ÿไธญ",
+                "cluster/repl/inspect": "ๆฃ€ๆŸฅๆ–‡ไปถ",
+                "cluster/repl/inspectbtn": "ๆ˜พ็คบๅ‰ฏๆœฌ",
+                "cluster/repl/issues": "{0} ไธช้—ฎ้ข˜",
+                "cluster/repl/lastrun": "ไธŠๆฌก่ฟ่กŒ {0}",
+                "cluster/repl/nocopy": "ๆ— ๅฅๅบทๅ‰ฏๆœฌ",
+                "cluster/repl/nolocations": "ๆฒกๆœ‰ๅ‰ฏๆœฌ่ฎฐๅฝ•",
+                "cluster/repl/notasks": "ๆฒกๆœ‰ๅคๅˆถไปปๅŠก",
+                "cluster/repl/notfound": "ๅœจๅ‘ฝๅ็ฉบ้—ดไธญๆ‰พไธๅˆฐๆญค่ทฏๅพ„",
+                "cluster/repl/over": "ๅคšไฝ™ๅ‰ฏๆœฌ",
+                "cluster/repl/plan": "็ซ‹ๅณไฟฎๅค",
+                "cluster/repl/planned": "่ง„ๅˆ’ๅ™จๅทฒ่ฟ่กŒ๏ผš{0} ไธชๅŠ ๅ…ฅ้˜Ÿๅˆ—๏ผŒ{1} ไธชๅˆ ้™ค๏ผŒ{2} ไธชๆ ‡่ฎฐไธบ่ฟ‡ๆœŸ",
+                "cluster/repl/planner": "่ง„ๅˆ’ๅ™จ",
+                "cluster/repl/primary": "ไธป",
+                "cluster/repl/reason/evacuate": "ๆ’ค็ฆป",
+                "cluster/repl/reason/repair": "ไฟฎๅค",
+                "cluster/repl/reason/replica": "ๆ–ฐๅขžๅ‰ฏๆœฌ",
+                "cluster/repl/route": "ๆฅๆบ / ็›ฎ็š„ๅœฐ",
+                "cluster/repl/stale": "่ฟ‡ๆœŸๅ‰ฏๆœฌ",
+                "cluster/repl/task": "ไปปๅŠก",
+                "cluster/repl/title": "ๅ‰ฏๆœฌๅคๅˆถ",
+                "cluster/repl/under": "ๅ‰ฏๆœฌไธ่ถณ",
+                "cluster/repl/verified": "ๅทฒ้ชŒ่ฏ {0} ไปฝๅ‰ฏๆœฌ๏ผˆ{1}๏ผ‰๏ผŒ{2} ไปฝ่ฟ‡ๆœŸ",
+                "cluster/repl/verify": "้ชŒ่ฏๆญค่Š‚็‚น",
+                "cluster/requestfailed": "่ฏทๆฑ‚ๅคฑ่ดฅ",
+                "cluster/resume": "ๆขๅค",
+                "cluster/revoke": "ๆ’ค้”€",
+                "cluster/route": "่ทฏๅพ„",
+                "cluster/route/direct": "็›ด่ฟž",
+                "cluster/route/none": "ๆ— ่ทฏๅพ„",
+                "cluster/route/relay": "็ป็”ฑ {0} ไธญ็ปง",
+                "cluster/route/tunnel": "้šง้“",
+                "cluster/save": "ไฟๅญ˜",
+                "cluster/saved": "ๅทฒไฟๅญ˜",
+                "cluster/sched/cpu": "CPU ็ฉบ้—ฒ",
+                "cluster/sched/cpu/hint": "ไผ˜ๅ…ˆ้€‰ๆ‹ฉ็ฉบ้—ฒ็š„ๅค„็†ๅ™จ",
+                "cluster/sched/degraded": "ๅฅๅบท็Šถๆ€",
+                "cluster/sched/degraded/hint": "้ฟๅผ€ๆ€ง่ƒฝไธไฝณ็š„่Š‚็‚น",
+                "cluster/sched/disk": "ๅฏ็”จ็ฃ็›˜",
+                "cluster/sched/disk/hint": "ไผ˜ๅ…ˆ้€‰ๆ‹ฉๆœ‰ๅขž้•ฟ็ฉบ้—ด็š„่Š‚็‚น",
+                "cluster/sched/diskfree": "็ฃ็›˜ๅ‰ฉไฝ™",
+                "cluster/sched/diversity": "็ซ™็‚นๅˆ†ๆ•ฃ",
+                "cluster/sched/diversity/hint": "ๆŠŠ็ฌฌไบŒไปฝๅ‰ฏๆœฌๆ”พๅœจ็ฆป็ฌฌไธ€ไปฝ่พƒ่ฟœ็š„ๅœฐๆ–น๏ผŒ่ฟ™ๆ ทๆŸไธช็ซ™็‚นๅœๆœบๆ—ถไธไผšไธขๅคฑๆ•ฐๆฎ",
+                "cluster/sched/explain": "ไปปๅŠกไธบไฝ•่ขซๅˆ†้…ๅˆฐ้‚ฃ้‡Œ๏ผŸ",
+                "cluster/sched/explainbtn": "่ฏดๆ˜Ž",
+                "cluster/sched/explainhint": "ไปŽ้›†็พคไปปๅŠก้กต้ขๅคๅˆถไปปๅŠก ID๏ผŒๅณๅฏๆŸฅ็œ‹ๅˆ†้…ๆ—ถ็š„ๆŽ’ๅใ€‚",
+                "cluster/sched/factor/cpu free": "CPU ็ฉบ้—ฒ",
+                "cluster/sched/factor/degraded": "ๆ€ง่ƒฝไธ‹้™",
+                "cluster/sched/factor/disk free": "็ฃ็›˜ๅ‰ฉไฝ™",
+                "cluster/sched/factor/locality": "ๆ•ฐๆฎไฝ็ฝฎ",
+                "cluster/sched/factor/memory free": "ๅ†…ๅญ˜ๅ‰ฉไฝ™",
+                "cluster/sched/factor/network distance": "็ฝ‘็ปœ่ท็ฆป",
+                "cluster/sched/factor/preferred features": "ๅๅฅฝๅŠŸ่ƒฝ",
+                "cluster/sched/factor/queue depth": "้˜Ÿๅˆ—้•ฟๅบฆ",
+                "cluster/sched/factor/site diversity": "็ซ™็‚นๅˆ†ๆ•ฃ",
+                "cluster/sched/features": "{0} ้กนๅŠŸ่ƒฝ",
+                "cluster/sched/from": "ๆฅๆบ / ็›ฎ็š„ๅœฐ",
+                "cluster/sched/here": "ๆœฌๅœฐ",
+                "cluster/sched/hint": "ไปปๅŠกใ€ๆ–ฐๆ–‡ไปถๅ’Œ้ขๅค–ๅ‰ฏๆœฌไผšไบค็ป™ๅˆ†ๆ•ฐๆœ€้ซ˜็š„่Š‚็‚นใ€‚่ฐƒ้ซ˜ๆƒ้‡ๅฏ่ฎฉ่ฏฅ้กน็‰น่ดจๆ›ด้‡่ฆใ€‚ๅฎž้™…ๅˆ†ๆ•ฐๆ˜พ็คบๅœจ้›†็พคไฟกๆฏ้กต้ขใ€‚",
+                "cluster/sched/infohint": "ไพ้›†็พค่ฎพ็ฝฎไธญ็š„ๆƒ้‡๏ผŒๅ„่Š‚็‚นๅฝ“ๅ‰็š„ๅˆ†ๆ•ฐใ€‚ไปปๅŠกใ€ๆ–ฐๆ–‡ไปถๅ’Œ้ขๅค–ๅ‰ฏๆœฌไผšไบค็ป™ๅˆ†ๆ•ฐๆœ€้ซ˜่€…ใ€‚",
+                "cluster/sched/latency": "็ฝ‘็ปœ่ท็ฆป",
+                "cluster/sched/latency/hint": "้ฟๅผ€่ท็ฆปๆ•ฐๆฎๅคช่ฟœ็š„่Š‚็‚น",
+                "cluster/sched/locality": "ๆ•ฐๆฎไฝ็ฝฎ",
+                "cluster/sched/locality/hint": "ไผ˜ๅ…ˆ้€‰ๆ‹ฉๅทฒๆŒๆœ‰ๆ•ฐๆฎ็š„่Š‚็‚น",
+                "cluster/sched/matrixhint": "ๆฏๅฏน่Š‚็‚นไน‹้—ด็š„ๅพ€่ฟ”ๆ—ถ้—ด๏ผˆๆฏซ็ง’๏ผ‰ใ€‚ๆ–‡ไปถ็š„ๅ‰ฏๆœฌไผšๅˆ†ๆ•ฃๅˆฐ่ท็ฆปๆœ€่ฟœ็š„่Š‚็‚นไธŠ๏ผŒๅ› ไธบ่ท็ฆป้€šๅธธไปฃ่กจไธๅŒ็š„็ซ™็‚นใ€‚",
+                "cluster/sched/mem": "ๅ†…ๅญ˜ๅ‰ฉไฝ™",
+                "cluster/sched/memory": "ๅฏ็”จๅ†…ๅญ˜",
+                "cluster/sched/memory/hint": "ไผ˜ๅ…ˆ้€‰ๆ‹ฉๆœ‰ๅฏŒไฝ™ๅ†…ๅญ˜็š„่Š‚็‚น",
+                "cluster/sched/next": "ไธ‹ไธ€ไธชไปปๅŠกๅฐ†ๅœจ {0} ่ฟ่กŒ",
+                "cluster/sched/nojob": "ๆญค้›†็พคไธญๆฒกๆœ‰่ฟ™ไธชไปปๅŠก",
+                "cluster/sched/nonodes": "ๆฒกๆœ‰ๅฏ่ฏ„ๅˆ†็š„่Š‚็‚น",
+                "cluster/sched/ping": "ๅพ€่ฟ”ๆ—ถ้—ด",
+                "cluster/sched/preferred": "ๅๅฅฝๅŠŸ่ƒฝ",
+                "cluster/sched/preferred/hint": "ไผ˜ๅ…ˆ้€‰ๆ‹ฉๅ…ทๅค‡ไปปๅŠกๆ‰€่ฆๆฑ‚็š„ๅฏ้€‰ๅŠŸ่ƒฝ็š„่Š‚็‚น",
+                "cluster/sched/queue": "้˜Ÿๅˆ—้•ฟๅบฆ",
+                "cluster/sched/queue/hint": "้ฟๅผ€ๅทฒ็ป็นๅฟ™็š„่Š‚็‚น",
+                "cluster/sched/reset": "ๆขๅค้ป˜่ฎคๅ€ผ",
+                "cluster/sched/restored": "ๅทฒๆขๅค้ป˜่ฎคๆƒ้‡",
+                "cluster/sched/save": "ไฟๅญ˜ๆƒ้‡",
+                "cluster/sched/saved": "ๆƒ้‡ๅทฒๅบ”็”จๅˆฐๆ•ดไธช้›†็พค",
+                "cluster/sched/score": "ๅˆ†ๆ•ฐ",
+                "cluster/sched/skipped": "่ทณ่ฟ‡",
+                "cluster/sched/title": "่ฐƒๅบฆ",
+                "cluster/sched/unmeasured": "ๅฐšๆœชๆต‹้‡",
+                "cluster/settings/tagline": "่ฎพ็ฝฎๆญค่Š‚็‚น๏ผŒไปฅๅŠ้›†็พคๅฆ‚ไฝ•ๅญ˜ๅ‚จๆ–‡ไปถๅ’Œ่ฟ่กŒไปปๅŠก",
+                "cluster/settings/title": "้›†็พค่ฎพ็ฝฎ",
+                "cluster/state": "็Šถๆ€",
+                "cluster/state/cancelled": "ๅทฒๅ–ๆถˆ",
+                "cluster/state/committed": "ๅทฒๅ†™ๅ…ฅ",
+                "cluster/state/degraded": "ๆ€ง่ƒฝไธ‹้™",
+                "cluster/state/done": "ๅฎŒๆˆ",
+                "cluster/state/draining": "ๆŽ’็ฉบไธญ",
+                "cluster/state/failed": "ๅคฑ่ดฅ",
+                "cluster/state/maintenance": "็ปดๆŠคไธญ",
+                "cluster/state/offline": "็ฆป็บฟ",
+                "cluster/state/online": "ๅœจ็บฟ",
+                "cluster/state/pending": "ๅพ…ๅค„็†",
+                "cluster/state/queued": "ๆŽ’้˜Ÿไธญ",
+                "cluster/state/running": "่ฟ่กŒไธญ",
+                "cluster/state/scheduled": "ๅทฒ่ฐƒๅบฆ",
+                "cluster/state/stale": "่ฟ‡ๆœŸ",
+                "cluster/state/succeeded": "ๆˆๅŠŸ",
+                "cluster/state/unknown": "ๆœช็Ÿฅ",
+                "cluster/state/verified": "ๅทฒ้ชŒ่ฏ",
+                "cluster/state/writing": "ๅ†™ๅ…ฅไธญ",
+                "cluster/storage": "ๅญ˜ๅ‚จ็ฉบ้—ด",
+                "cluster/testing": "ๆต‹่ฏ•ไธญ...",
+                "cluster/thisnode": "ๆญค่Š‚็‚น",
+                "cluster/tokenhint": "ๅชๆœ‰่ฎพ็ฝฎไบ†ๅฏนๅค–็ฝ‘ๅ€็š„่Š‚็‚นๆ‰่ƒฝ็”Ÿๆˆไปค็‰Œใ€‚ๅฐ†ไปค็‰Œ็ฒ˜่ดดๅˆฐๅฆไธ€ไธช่Š‚็‚น็š„้›†็พค่ฎพ็ฝฎไธญใ€‚",
+                "cluster/tokenline": "ไบŽ {0} ่ฟ‡ๆœŸ ยท ๅทฒไฝฟ็”จ {1} ๆฌก",
+                "cluster/ttl/1h": "1 ๅฐๆ—ถ",
+                "cluster/ttl/24h": "24 ๅฐๆ—ถ",
+                "cluster/ttl/7d": "7 ๅคฉ",
+                "cluster/tunnel": "้šง้“",
+                "cluster/tunnelhere": "้šง้“่ฟžๅˆฐๆญค่Š‚็‚น",
+                "cluster/tunnelvia": "็ป็”ฑ {0} ็š„้šง้“",
+                "cluster/unreachable": "ๆ— ๆณ•่ฟžๆŽฅ",
+                "cluster/updated": "ๆ›ดๆ–ฐไบŽ {0}",
+                "cluster/uptime": "่ฟ่กŒๆ—ถ้—ด",
+                "cluster/uptimevalue": "{0} ๅฐๆ—ถ {1} ๅˆ†",
+                "cluster/version": "็‰ˆๆœฌ",
+                "cluster/via": "็ป็”ฑ {0}",
+                "cluster/vol/add": "ๆไพ›ๆญค่Š‚็‚นไธŠ็š„ๆ–‡ไปถๅคน",
+                "cluster/vol/addbtn": "ๆทปๅŠ ๅญ˜ๅ‚จๅท",
+                "cluster/vol/added": "ๅทฒๆทปๅŠ ๅญ˜ๅ‚จๅท",
+                "cluster/vol/autoro": "่‡ชๅŠจๅฐ†ๅฟซๆปก็š„ๅญ˜ๅ‚จๅท่ฎพไธบๅช่ฏป",
+                "cluster/vol/autorohint": "ๅบ”็”จๅˆฐๆฏไธช่Š‚็‚นใ€‚ๅ‰ฉไฝ™็ฉบ้—ดไฝŽไบŽ 5% ็š„ๅญ˜ๅ‚จๅทไผšๅœๆญขๆŽฅๆ”ถๆ–ฐๆ–‡ไปถ๏ผŒ้ซ˜ไบŽ 7% ๆ—ถๅ†ๆขๅคใ€‚ๅ…ณ้—ญๆญค้กนๅฏ่ฎฉๅญ˜ๅ‚จๅท็”จๆปก๏ผ›ๅ…ˆๅ‰ๅ› ๆญค่ขซ้”ๅฎš็š„ๅญ˜ๅ‚จๅทไผšๅœจไธ€ๅˆ†้’Ÿๅ†…ๆขๅคๅฏๅ†™ใ€‚",
+                "cluster/vol/autorooff": "ๅญ˜ๅ‚จๅท็ŽฐๅœจๅฏไปฅๅฎŒๅ…จ็”จๆปก",
+                "cluster/vol/autoroon": "ๅฟซๆปก็š„ๅญ˜ๅ‚จๅทๅฐ†ไผš่ฎพไธบๅช่ฏป",
+                "cluster/vol/confirmevac": "่ฆๅฐ†โ€œ{0}โ€ไธŠ็š„ๆ‰€ๆœ‰ๆ–‡ไปถ็งป่ตฐๅ—๏ผŸๆญคๅญ˜ๅ‚จๅทไผšๅ˜ๆˆๅช่ฏป๏ผŒๅนถๅœจๆ‰€ๆœ‰ๅ‰ฏๆœฌ้ƒฝๅญ˜ๅœจไบŽๅˆซๅค„ๅŽ้€€ๅฝนใ€‚",
+                "cluster/vol/confirmremove": "่ฆๅœๆญขๆไพ›โ€œ{0}โ€ๅ—๏ผŸๆ–‡ไปถไผšไฟ็•™ๅœจ็ฃ็›˜ไธŠ๏ผŒไฝ†ๅชๅญ˜ๅœจ้‚ฃ้‡Œ็š„ๅ‰ฏๆœฌๅฐ†ๆ— ๆณ•ไฝฟ็”จใ€‚",
+                "cluster/vol/evacstarted": "ๅทฒๅผ€ๅง‹ๆ’ค็ฆป๏ผš{0} ไธชๆ–‡ไปถๅพ…็งปๅŠจ",
+                "cluster/vol/evacuate": "ๆ’ค็ฆป",
+                "cluster/vol/evacuating": "ๆ’ค็ฆปไธญ",
+                "cluster/vol/files": "ๆ–‡ไปถ",
+                "cluster/vol/full": "ๅฟซๆปก",
+                "cluster/vol/hint": "ๅ†™ๅ…ฅ cluster:/ ็š„ๆ–‡ไปถไผšไปฅๆ™ฎ้€šๆ–‡ไปถๅญ˜ๆ”พๅœจๆไพ›็š„ๆ–‡ไปถๅคนไธญใ€‚ๆ–‡ไปถๅคนไธญๅŽŸๆœ‰็š„ๆ–‡ไปถไผšๅœจไธ‹ๆฌก้‡ๆ–ฐๆ‰ซๆๆ—ถ็บณๅ…ฅๅ‘ฝๅ็ฉบ้—ดใ€‚้›†็พค่‡ณๅฐ‘ๆœ‰ไธ€ไธชๅญ˜ๅ‚จๅทๅŽ๏ผŒcluster:/ ็ฃ็›˜ๆ‰ไผšๅ‡บ็Žฐๅœจๆ–‡ไปถ็ฎก็†ๅ™จไธญใ€‚",
+                "cluster/vol/none": "ๅฐšๆ— ๅญ˜ๅ‚จๅทใ€‚ๆทปๅŠ ๆญค่Š‚็‚นไธŠ็š„ๆ–‡ไปถๅคนๅณๅฏๅผ€ๅง‹ๅœจ cluster:/ ไธญๅญ˜ๆ”พๆ–‡ไปถ",
+                "cluster/vol/readonly": "่ฎพไธบๅช่ฏป",
+                "cluster/vol/rescan": "้‡ๆ–ฐๆ‰ซๆๆญค่Š‚็‚น",
+                "cluster/vol/rescanned": "้‡ๆ–ฐๆ‰ซๆๅฎŒๆˆ๏ผš็บณๅ…ฅ {0} ไธช๏ผŒ่ฟ‡ๆœŸ {1} ไธช",
+                "cluster/vol/ro": "ๅช่ฏป",
+                "cluster/vol/space": "ๅ‰ฉไฝ™ / ๆ€ปๅฎน้‡",
+                "cluster/vol/summary": "{0} ไธชๅญ˜ๅ‚จๅท๏ผŒๅ‰ฉไฝ™ {1}",
+                "cluster/vol/volume": "ๅญ˜ๅ‚จๅท",
+                "cluster/vol/writable": "่ฎพไธบๅฏๅ†™",
+                "cluster/waitinghost": "ๆญฃๅœจ็ญ‰ๅพ…ๅฏ่ฟžๆŽฅ็š„ๆˆๅ‘˜",
+                "cluster/when": "ๆ—ถ้—ด",
+                "cluster/willtunnel": "ๅŠ ๅ…ฅ้›†็พคๅŽๅฐ†้€š่ฟ‡ๆˆๅ‘˜ๅปบ็ซ‹้šง้“",
+                "cluster/you": "ไฝ ",
+                "msg/AGI gateway not ready": "AGI ็ฝ‘ๅ…ณๅฐšๆœชๅฐฑ็ปช",
+                "msg/Invalid MAC address": "MAC ๅœฐๅ€ๆ— ๆ•ˆ",
+                "msg/POST required": "ๅฟ…้กปไฝฟ็”จ POST",
+                "msg/URL must start with http:// or https://": "็ฝ‘ๅ€ๅฟ…้กปไปฅ http:// ๆˆ– https:// ๅผ€ๅคด",
+                "msg/a job id is required": "ๅฟ…้กปๆไพ›ไปปๅŠก ID",
+                "msg/a map/reduce job needs a dataset pattern": "map/reduce ไปปๅŠก้œ€่ฆๆ•ฐๆฎ้›†ๆจกๅผ",
+                "msg/a map/reduce script must define mapper(files, emit) and reducer(key, values)": "map/reduce ่„šๆœฌๅฟ…้กปๅฎšไน‰ mapper(files, emit) ๅ’Œ reducer(key, values)",
+                "msg/advertise URL must start with http:// or https://": "ๅฏนๅค–็ฝ‘ๅ€ๅฟ…้กปไปฅ http:// ๆˆ– https:// ๅผ€ๅคด",
+                "msg/args must be valid JSON": "ๅ‚ๆ•ฐๅฟ…้กปๆ˜ฏๆœ‰ๆ•ˆ็š„ JSON",
+                "msg/cannot remove the namespace root": "ๆ— ๆณ•็งป้™คๅ‘ฝๅ็ฉบ้—ด็š„ๆ น็›ฎๅฝ•",
+                "msg/checksum mismatch": "ๆ ก้ชŒๅ’ŒไธๅŒน้…",
+                "msg/chunk checksum mismatch": "ๅˆ†ๅ—ๆ ก้ชŒๅ’ŒไธๅŒน้…",
+                "msg/chunk exceeds file size": "ๅˆ†ๅ—่ถ…่ฟ‡ๆ–‡ไปถๅคงๅฐ",
+                "msg/chunk index out of range": "ๅˆ†ๅ—็ดขๅผ•่ถ…ๅ‡บ่Œƒๅ›ด",
+                "msg/chunk size invalid": "ๅˆ†ๅ—ๅคงๅฐๆ— ๆ•ˆ",
+                "msg/cluster jobs not available": "้›†็พคไปปๅŠกไธๅฏ็”จ",
+                "msg/cluster name cannot be empty": "้›†็พคๅ็งฐไธ่ƒฝไธบ็ฉบ",
+                "msg/cluster name required": "ๅฟ…้กป่พ“ๅ…ฅ้›†็พคๅ็งฐ",
+                "msg/directory not empty": "ๆ–‡ไปถๅคนไธๆ˜ฏ็ฉบ็š„",
+                "msg/disk usage not supported on this platform": "ๆญคๅนณๅฐไธๆ”ฏๆŒ็ฃ็›˜็”จ้‡ๆŸฅ่ฏข",
+                "msg/drive required": "ๅฟ…้กป้€‰ๆ‹ฉ็ฃ็›˜",
+                "msg/empty response": "ๅ“ๅบ”ไธบ็ฉบ",
+                "msg/enabled must be true or false": "enabled ๅฟ…้กปๆ˜ฏ true ๆˆ– false",
+                "msg/event bus not available": "ไบ‹ไปถๆ€ป็บฟไธๅฏ็”จ",
+                "msg/event data too large (64 KB max)": "ไบ‹ไปถๆ•ฐๆฎๅคชๅคง๏ผˆไธŠ้™ 64 KB๏ผ‰",
+                "msg/event type required": "ๅฟ…้กปๆไพ›ไบ‹ไปถ็ฑปๅž‹",
+                "msg/evict notice is not for this node": "็งป้™ค้€š็Ÿฅไธๆ˜ฏๅ‘็ป™ๆญค่Š‚็‚น็š„",
+                "msg/file changed since the copy was planned": "ๆ–‡ไปถๅœจ่ง„ๅˆ’ๅคๅˆถๅŽๅทฒๆ›ดๆ”น",
+                "msg/file no longer exists in the namespace": "ๆ–‡ไปถๅทฒไธๅญ˜ๅœจไบŽๅ‘ฝๅ็ฉบ้—ดไธญ",
+                "msg/file not found on volume": "ๅญ˜ๅ‚จๅทไธŠๆ‰พไธๅˆฐๆ–‡ไปถ",
+                "msg/folder path required": "ๅฟ…้กป่พ“ๅ…ฅๆ–‡ไปถๅคน่ทฏๅพ„",
+                "msg/folder required": "ๅฟ…้กป่พ“ๅ…ฅๆ–‡ไปถๅคน",
+                "msg/hook not found": "ๆ‰พไธๅˆฐ้’ฉๅญ",
+                "msg/insufficient cpu cores": "CPU ๆ ธๅฟƒไธ่ถณ",
+                "msg/insufficient memory": "ๅ†…ๅญ˜ไธ่ถณ",
+                "msg/invalid append payload": "ๆ— ๆ•ˆ็š„่ฟฝๅŠ ๆ•ฐๆฎ",
+                "msg/invalid batch": "ๆ— ๆ•ˆ็š„ๆ‰นๆฌก",
+                "msg/invalid begin request": "ๆ— ๆ•ˆ็š„ๅผ€ๅง‹่ฏทๆฑ‚",
+                "msg/invalid entry": "ๆ— ๆ•ˆ็š„ๆก็›ฎ",
+                "msg/invalid heartbeat": "ๆ— ๆ•ˆ็š„ๅฟƒ่ทณ",
+                "msg/invalid job spec": "ๆ— ๆ•ˆ็š„ไปปๅŠก่ง„ๆ ผ",
+                "msg/invalid join request": "ๆ— ๆ•ˆ็š„ๅŠ ๅ…ฅ่ฏทๆฑ‚",
+                "msg/invalid lease": "ๆ— ๆ•ˆ็š„็งŸ็บฆ",
+                "msg/invalid node URL": "่Š‚็‚น็ฝ‘ๅ€ๆ— ๆ•ˆ",
+                "msg/invalid node public key": "่Š‚็‚นๅ…ฌ้’ฅๆ— ๆ•ˆ",
+                "msg/invalid node record": "ๆ— ๆ•ˆ็š„่Š‚็‚น่ฎฐๅฝ•",
+                "msg/invalid range": "ๆ— ๆ•ˆ็š„่Œƒๅ›ด",
+                "msg/invalid rename": "ๆ— ๆ•ˆ็š„้‡ๅ‘ฝๅ",
+                "msg/invalid report": "ๆ— ๆ•ˆ็š„ๆŠฅๅ‘Š",
+                "msg/invalid request": "ๆ— ๆ•ˆ็š„่ฏทๆฑ‚",
+                "msg/invalid sync payload": "ๆ— ๆ•ˆ็š„ๅŒๆญฅๆ•ฐๆฎ",
+                "msg/invalid task": "ๆ— ๆ•ˆ็š„ไปปๅŠก",
+                "msg/invalid username": "็”จๆˆทๅๆ— ๆ•ˆ",
+                "msg/is a directory": "่ฟ™ๆ˜ฏๆ–‡ไปถๅคน",
+                "msg/job cancelled": "ไปปๅŠกๅทฒๅ–ๆถˆ",
+                "msg/job id required": "ๅฟ…้กปๆไพ›ไปปๅŠก ID",
+                "msg/job is limited to other nodes": "ๆญคไปปๅŠกๅช่ƒฝๅœจๅ…ถไป–่Š‚็‚นไธŠ่ฟ่กŒ",
+                "msg/job not found": "ๆ‰พไธๅˆฐไปปๅŠก",
+                "msg/job owner is required": "ๅฟ…้กปๆไพ›ไปปๅŠกๆ‰€ๆœ‰่€…",
+                "msg/job script is empty": "ไปปๅŠก่„šๆœฌไธบ็ฉบ",
+                "msg/job timed out": "ไปปๅŠก่ถ…ๆ—ถ",
+                "msg/join response does not match the token's cluster": "ๅŠ ๅ…ฅๅ“ๅบ”ไธŽไปค็‰Œๆ‰€ๅฑž็š„้›†็พคไธๅŒน้…",
+                "msg/join token has an invalid node URL": "ๅŠ ๅ…ฅไปค็‰Œไธญ็š„่Š‚็‚น็ฝ‘ๅ€ๆ— ๆ•ˆ",
+                "msg/join token is corrupted": "ๅŠ ๅ…ฅไปค็‰ŒๅทฒๆŸๅ",
+                "msg/join token is for another cluster": "ๆญคๅŠ ๅ…ฅไปค็‰ŒๅฑžไบŽๅฆไธ€ไธช้›†็พค",
+                "msg/join token is incomplete": "ๅŠ ๅ…ฅไปค็‰ŒไธๅฎŒๆ•ด",
+                "msg/join token is invalid or expired": "ๅŠ ๅ…ฅไปค็‰Œๆ— ๆ•ˆๆˆ–ๅทฒ่ฟ‡ๆœŸ",
+                "msg/join token required": "ๅฟ…้กปๆไพ›ๅŠ ๅ…ฅไปค็‰Œ",
+                "msg/metadata store not available": "ๅ…ƒๆ•ฐๆฎๅญ˜ๅ‚จไธๅฏ็”จ",
+                "msg/no cluster volume available; add one in System Settings > Cluster Settings > Storage": "ๆฒกๆœ‰ๅฏ็”จ็š„้›†็พคๅญ˜ๅ‚จๅท๏ผ›่ฏทๅœจโ€œ็ณป็ปŸ่ฎพ็ฝฎ > ้›†็พค่ฎพ็ฝฎ > ๅญ˜ๅ‚จ็ฉบ้—ดโ€ไธญๆทปๅŠ ",
+                "msg/no healthy copy of this file is reachable right now": "็›ฎๅ‰ๆ— ๆณ•่ฟžๆŽฅๅˆฐๆญคๆ–‡ไปถ็š„ไปปไฝ•ๅฅๅบทๅ‰ฏๆœฌ",
+                "msg/no node is available": "ๆฒกๆœ‰ๅฏ็”จ็š„่Š‚็‚น",
+                "msg/node already has enough copies in flight": "ๆญค่Š‚็‚นๅทฒๆœ‰่ถณๅคŸ็š„ๅคๅˆถๆญฃๅœจ่ฟ›่กŒ",
+                "msg/node id is required": "ๅฟ…้กปๆไพ›่Š‚็‚น ID",
+                "msg/node id required": "ๅฟ…้กปๆไพ›่Š‚็‚น ID",
+                "msg/node is DEGRADED": "่Š‚็‚นๆ€ง่ƒฝไธ‹้™",
+                "msg/node is DRAINING": "่Š‚็‚นๆŽ’็ฉบไธญ",
+                "msg/node is MAINTENANCE": "่Š‚็‚น็ปดๆŠคไธญ",
+                "msg/node is OFFLINE": "่Š‚็‚นๅทฒ็ฆป็บฟ",
+                "msg/node is UNKNOWN": "่Š‚็‚น็Šถๆ€ๆœช็Ÿฅ",
+                "msg/node is degraded": "่Š‚็‚นๆ€ง่ƒฝไธ‹้™",
+                "msg/node is draining": "่Š‚็‚นๆŽ’็ฉบไธญ",
+                "msg/node is maintenance": "่Š‚็‚น็ปดๆŠคไธญ",
+                "msg/node is not available": "่Š‚็‚นไธๅฏ็”จ",
+                "msg/node is offline": "่Š‚็‚นๅทฒ็ฆป็บฟ",
+                "msg/node is unknown": "่Š‚็‚น็Šถๆ€ๆœช็Ÿฅ",
+                "msg/node lost while running the job": "่ฟ่กŒไปปๅŠกๆ—ถ่Š‚็‚นๅคฑๅŽป่ฟžๆŽฅ",
+                "msg/node name cannot be empty": "่Š‚็‚นๅ็งฐไธ่ƒฝไธบ็ฉบ",
+                "msg/node not found": "ๆ‰พไธๅˆฐ่Š‚็‚น",
+                "msg/node refused this job earlier": "ๆญค่Š‚็‚นๅ…ˆๅ‰ๆ‹’็ปไบ†่ฟ™ไธชไปปๅŠก",
+                "msg/not a directory": "ไธๆ˜ฏๆ–‡ไปถๅคน",
+                "msg/not a file": "ไธๆ˜ฏๆ–‡ไปถ",
+                "msg/not an ArozOS join token": "่ฟ™ไธๆ˜ฏ ArozOS ๅŠ ๅ…ฅไปค็‰Œ",
+                "msg/not in a cluster": "ๆœชๅŠ ๅ…ฅ้›†็พค",
+                "msg/not logged in": "ๅฐšๆœช็™ปๅฝ•",
+                "msg/offset beyond end of file": "ๅ็งป่ถ…ๅ‡บๆ–‡ไปถๆœซๅฐพ",
+                "msg/only the metadata leader assigns jobs": "ๅชๆœ‰ๅ…ƒๆ•ฐๆฎไธป่Š‚็‚นๅฏไปฅๅˆ†้…ไปปๅŠก",
+                "msg/only the metadata leader assigns replication tasks": "ๅชๆœ‰ๅ…ƒๆ•ฐๆฎไธป่Š‚็‚นๅฏไปฅๅˆ†้…ๅคๅˆถไปปๅŠก",
+                "msg/owner, script and at least one event type are required": "ๅฟ…้กปๆไพ›ๆ‰€ๆœ‰่€…ใ€่„šๆœฌๅ’Œ่‡ณๅฐ‘ไธ€็งไบ‹ไปถ็ฑปๅž‹",
+                "msg/path already exists": "่ทฏๅพ„ๅทฒๅญ˜ๅœจ",
+                "msg/path escapes the volume": "่ทฏๅพ„่ถ…ๅ‡บไบ†ๅญ˜ๅ‚จๅท่Œƒๅ›ด",
+                "msg/path not found in the cluster namespace": "ๅœจ้›†็พคๅ‘ฝๅ็ฉบ้—ดไธญๆ‰พไธๅˆฐๆญค่ทฏๅพ„",
+                "msg/path required": "ๅฟ…้กป่พ“ๅ…ฅ่ทฏๅพ„",
+                "msg/peer has no reachable endpoint": "ๅฏนๆ–น่Š‚็‚นๆฒกๆœ‰ๅฏ่ฟžๆŽฅ็š„็ซฏ็‚น",
+                "msg/permission denied": "ๆƒ้™ไธ่ถณ",
+                "msg/previous node stopped reporting": "ๅ…ˆๅ‰็š„่Š‚็‚นๅœๆญขๆŠฅๅ‘Š",
+                "msg/refusing to delete the volume root": "ๆ‹’็ปๅˆ ้™คๅญ˜ๅ‚จๅทๆ น็›ฎๅฝ•",
+                "msg/refusing to drop the last healthy copy": "ๆ‹’็ปๅˆ ้™คๆœ€ๅŽไธ€ไปฝๅฅๅบทๅ‰ฏๆœฌ",
+                "msg/replicas must be between 0 and 16": "ๅ‰ฏๆœฌๆ•ฐๅฟ…้กปๅœจ 0 ๅˆฐ 16 ไน‹้—ด",
+                "msg/replicas must be between 1 and 16": "ๅ‰ฏๆœฌๆ•ฐๅฟ…้กปๅœจ 1 ๅˆฐ 16 ไน‹้—ด",
+                "msg/request is not signed": "่ฏทๆฑ‚ๆœช็ญพๅ",
+                "msg/request signature invalid": "่ฏทๆฑ‚็ญพๅๆ— ๆ•ˆ",
+                "msg/request targets another cluster": "่ฏทๆฑ‚็š„็›ฎๆ ‡ๆ˜ฏๅฆไธ€ไธช้›†็พค",
+                "msg/request timestamp outside the accepted window": "่ฏทๆฑ‚็š„ๆ—ถ้—ดๆˆณ่ถ…ๅ‡บๅ…่ฎธ่Œƒๅ›ด๏ผˆ่ฏทๆฃ€ๆŸฅ่Š‚็‚น็š„ๆ—ถ้’Ÿ๏ผ‰",
+                "msg/requested log range was compacted, take a snapshot": "่ฏทๆฑ‚็š„ๆ—ฅๅฟ—่Œƒๅ›ดๅทฒ่ขซๅŽ‹็ผฉ๏ผŒ่ฏทๆ”น็”จๅฟซ็…ง",
+                "msg/script path required": "ๅฟ…้กปๆไพ›่„šๆœฌ่ทฏๅพ„",
+                "msg/sender is not a member of this cluster": "ๅ‘้€่€…ไธๆ˜ฏๆญค้›†็พค็š„ๆˆๅ‘˜",
+                "msg/shutting down": "ๆญฃๅœจๅ…ณ้—ญ",
+                "msg/source copy is not readable": "ๆฅๆบๅ‰ฏๆœฌๆ— ๆณ•่ฏปๅ–",
+                "msg/target node is not connected through a tunnel on this node": "็›ฎๆ ‡่Š‚็‚นๆฒกๆœ‰้€š่ฟ‡ๆญค่Š‚็‚น็š„้šง้“่ฟžๆŽฅ",
+                "msg/task is for another node": "ๆญคไปปๅŠกๅฑžไบŽๅฆไธ€ไธช่Š‚็‚น",
+                "msg/the URL answered but it is not an ArozOS cluster endpoint": "ๆญค็ฝ‘ๅ€ๆœ‰ๅ“ๅบ”๏ผŒไฝ†ไธๆ˜ฏ ArozOS ้›†็พค็ซฏ็‚น",
+                "msg/the job has no dataset pattern": "ๆญคไปปๅŠกๆฒกๆœ‰ๆ•ฐๆฎ้›†ๆจกๅผ",
+                "msg/the job owner has no account on this node": "ๆญค่Š‚็‚นไธŠๆฒกๆœ‰ไปปๅŠกๆ‰€ๆœ‰่€…็š„่ดฆๆˆท",
+                "msg/the mappers emitted more than 8 MB; narrow the dataset or emit fewer values": "mapper ่พ“ๅ‡บ่ถ…่ฟ‡ 8 MB๏ผ›่ฏท็ผฉๅฐๆ•ฐๆฎ้›†ๆˆ–ๅ‡ๅฐ‘่พ“ๅ‡บ็š„ๅ€ผ",
+                "msg/the user root itself cannot be a cluster volume; pick a sub folder such as /cluster": "็”จๆˆทๆ น็›ฎๅฝ•ๆœฌ่บซไธ่ƒฝไฝœไธบ้›†็พคๅญ˜ๅ‚จๅท๏ผ›่ฏท้€‰ๆ‹ฉๅญๆ–‡ไปถๅคน๏ผŒไพ‹ๅฆ‚ /cluster",
+                "msg/this folder is already a cluster volume": "ๆญคๆ–‡ไปถๅคนๅทฒ็ปๆ˜ฏ้›†็พคๅญ˜ๅ‚จๅท",
+                "msg/this job script defines no run(job) function": "ๆญคไปปๅŠก่„šๆœฌๆฒกๆœ‰ๅฎšไน‰ run(job) ๅ‡ฝๆ•ฐ",
+                "msg/this node cannot execute jobs": "ๆญค่Š‚็‚นๆ— ๆณ•่ฟ่กŒไปปๅŠก",
+                "msg/this node has no advertised URL; generate the join token on a node that other nodes can reach": "ๆญค่Š‚็‚นๆฒกๆœ‰ๅฏนๅค–็ฝ‘ๅ€๏ผ›่ฏทๅœจๅ…ถไป–่Š‚็‚นๅฏไปฅ่ฟžๆŽฅ็š„่Š‚็‚นไธŠ็”ŸๆˆๅŠ ๅ…ฅไปค็‰Œ",
+                "msg/this node is already part of a cluster, leave it first": "ๆญค่Š‚็‚นๅทฒๅŠ ๅ…ฅ้›†็พค๏ผŒ่ฏทๅ…ˆ็ฆปๅผ€",
+                "msg/this node is not part of a cluster": "ๆญค่Š‚็‚นๅฐšๆœชๅŠ ๅ…ฅ้›†็พค",
+                "msg/this node is not the identity origin of the cluster": "ๆญค่Š‚็‚นไธๆ˜ฏ้›†็พค็š„่บซไปฝๆบ",
+                "msg/this node is not the metadata leader": "ๆญค่Š‚็‚นไธๆ˜ฏๅ…ƒๆ•ฐๆฎไธป่Š‚็‚น",
+                "msg/timed out waiting for the job to finish": "็ญ‰ๅพ…ไปปๅŠกๅฎŒๆˆ่ถ…ๆ—ถ",
+                "msg/token id required": "ๅฟ…้กปๆไพ›ไปค็‰Œ ID",
+                "msg/token not found": "ๆ‰พไธๅˆฐไปค็‰Œ",
+                "msg/tunnel closed": "้šง้“ๅทฒๅ…ณ้—ญ",
+                "msg/unknown upload session": "ๆœช็Ÿฅ็š„ไธŠไผ ไผš่ฏ",
+                "msg/unreachable": "ๆ— ๆณ•่ฟžๆŽฅ",
+                "msg/unsupported node state": "ไธๆ”ฏๆŒ็š„่Š‚็‚น็Šถๆ€",
+                "msg/upload incomplete": "ไธŠไผ ๆœชๅฎŒๆˆ",
+                "msg/url required": "ๅฟ…้กป่พ“ๅ…ฅ็ฝ‘ๅ€",
+                "msg/use leave to remove this node": "่ฆ็งป้™คๆญค่Š‚็‚น่ฏทไฝฟ็”จโ€œ็ฆปๅผ€้›†็พคโ€",
+                "msg/user assertion expired": "็”จๆˆทๅฃฐๆ˜Žๅทฒ่ฟ‡ๆœŸ",
+                "msg/user assertion invalid": "็”จๆˆทๅฃฐๆ˜Žๆ— ๆ•ˆ",
+                "msg/user not found on the identity origin": "่บซไปฝๆบไธŠๆ‰พไธๅˆฐๆญค็”จๆˆท",
+                "msg/volume id required": "ๅฟ…้กปๆไพ›ๅญ˜ๅ‚จๅท ID",
+                "msg/volume is not on this node": "ๅญ˜ๅ‚จๅทไธๅœจๆญค่Š‚็‚นไธŠ",
+                "msg/volume not found": "ๆ‰พไธๅˆฐๅญ˜ๅ‚จๅท",
+                "msg/volume not found on this node": "ๆญค่Š‚็‚นไธŠๆ‰พไธๅˆฐๅญ˜ๅ‚จๅท",
+                "msgp/architecture {0} not allowed": "ไธๅ…่ฎธ็š„ๆžถๆž„ {0}",
+                "msgp/cancelled by {0}": "็”ฑ {0} ๅ–ๆถˆ",
+                "msgp/could not assemble the reduce input: {0}": "ๆ— ๆณ•็ป„ๅˆ reduce ็š„่พ“ๅ…ฅ๏ผš{0}",
+                "msgp/could not create a map task: {0}": "ๆ— ๆณ•ๅˆ›ๅปบ map ๅญไปปๅŠก๏ผš{0}",
+                "msgp/could not create the reduce task: {0}": "ๆ— ๆณ•ๅˆ›ๅปบ reduce ๅญไปปๅŠก๏ผš{0}",
+                "msgp/drive {0} is not a local drive on this node": "็ฃ็›˜ {0} ไธๆ˜ฏๆญค่Š‚็‚นไธŠ็š„ๆœฌๅœฐ็ฃ็›˜",
+                "msgp/hook script not found: {0}": "ๆ‰พไธๅˆฐ้’ฉๅญ่„šๆœฌ๏ผš{0}",
+                "msgp/invalid join response from {0}": "{0} ็š„ๅŠ ๅ…ฅๅ“ๅบ”ๆ— ๆ•ˆ",
+                "msgp/invalid node key encoding: {0}": "่Š‚็‚นๅฏ†้’ฅ็ผ–็ ๆ— ๆ•ˆ๏ผš{0}",
+                "msgp/invalid public key encoding: {0}": "ๅ…ฌ้’ฅ็ผ–็ ๆ— ๆ•ˆ๏ผš{0}",
+                "msgp/job timed out after {0}s": "ไปปๅŠกๅœจ {0} ็ง’ๅŽ่ถ…ๆ—ถ",
+                "msgp/local drive {0} is not mounted": "ๆœฌๅœฐ็ฃ็›˜ {0} ๆœชๆŒ‚่ฝฝ",
+                "msgp/missing feature {0}": "็ผบๅฐ‘ๅŠŸ่ƒฝ {0}",
+                "msgp/no cluster volume available; add one in System Settings > Cluster Settings > Storage ({0})": "ๆฒกๆœ‰ๅฏ็”จ็š„้›†็พคๅญ˜ๅ‚จๅท๏ผ›่ฏทๅœจโ€œ็ณป็ปŸ่ฎพ็ฝฎ > ้›†็พค่ฎพ็ฝฎ > ๅญ˜ๅ‚จ็ฉบ้—ดโ€ไธญๆทปๅŠ ๏ผˆ{0}๏ผ‰",
+                "msgp/no reachable copy of: {0}": "ๆ— ๆณ•่ฟžๆŽฅๅˆฐไปฅไธ‹ๆ–‡ไปถ็š„ๅ‰ฏๆœฌ๏ผš{0}",
+                "msgp/node {0} could not take the job: {1}": "่Š‚็‚น {0} ๆ— ๆณ•ๆŽฅๆ”ถๆญคไปปๅŠก๏ผš{1}",
+                "msgp/os {0} not allowed": "ไธๅ…่ฎธ็š„ๆ“ไฝœ็ณป็ปŸ {0}",
+                "msgp/permission denied: {0}": "ๆƒ้™ไธ่ถณ๏ผš{0}",
+                "msgp/remote node returned {0}: {1}": "่ฟœ็ซฏ่Š‚็‚น่ฟ”ๅ›ž {0}๏ผš{1}",
+                "msgp/script not found: {0}": "ๆ‰พไธๅˆฐ่„šๆœฌ๏ผš{0}",
+                "msgp/this volume holds the only copy of {0} file(s); evacuate it first": "ๆญคๅญ˜ๅ‚จๅทๆŒๆœ‰ {0} ไธชๆ–‡ไปถ็š„ๅ”ฏไธ€ๅ‰ฏๆœฌ๏ผ›่ฏทๅ…ˆๆ’ค็ฆป",
+                "msgp/unable to reach {0}: {1}": "ๆ— ๆณ•่ฟžๆŽฅๅˆฐ {0}๏ผš{1}",
+                "msgp/unreachable: {0}": "ๆ— ๆณ•่ฟžๆŽฅ๏ผš{0}",
+                "msgp/unsupported node URL scheme: {0}": "ไธๆ”ฏๆŒ็š„่Š‚็‚น็ฝ‘ๅ€ๅ่ฎฎ๏ผš{0}",
+                "msgp/weight {0} must be a number": "ๆƒ้‡ {0} ๅฟ…้กปๆ˜ฏๆ•ฐๅญ—",
+                "msgp/weight {0} must be between 0 and 1": "ๆƒ้‡ {0} ๅฟ…้กปๅœจ 0 ไธŽ 1 ไน‹้—ด",
+                "msgp/{0} produced an unreadable result": "{0} ไบง็”Ÿไบ†ๆ— ๆณ•่ฏปๅ–็š„็ป“ๆžœ"
+            },
+            "placeholder": {
+                "Name (optional)": "ๅ็งฐ๏ผˆๅฏ้€‰๏ผ‰",
+                "job id": "ไปปๅŠก ID",
+                "Nightly index": "ๆฏๆ™š็ดขๅผ•",
+                "50 per task": "ๆฏไธชๅญไปปๅŠก 50 ไธช"
+            },
+            "titles": {
+                "Re-detect capabilities": "้‡ๆ–ฐๆฃ€ๆต‹ๅŠŸ่ƒฝ"
+            }
+        },
+        "ja-jp": {
+            "name": "ๆ—ฅๆœฌ่ชž",
+            "fontFamily": "\"Meiryo UI\", \"Arial Unicode MS\", \"Hiragino Kaku Gothic Pro\"",
+            "strings": {
+                "cluster/ago/d": "{0} ๆ—ฅๅ‰",
+                "cluster/ago/h": "{0} ๆ™‚้–“ๅ‰",
+                "cluster/ago/m": "{0} ๅˆ†ๅ‰",
+                "cluster/ago/s": "{0} ็ง’ๅ‰",
+                "cluster/auto": "่‡ชๅ‹•๏ผˆๅˆฐ้”ๅฏ่ƒฝใชไปปๆ„ใฎใƒกใƒณใƒใƒผ๏ผ‰",
+                "cluster/cancel": "ใ‚ญใƒฃใƒณใ‚ปใƒซ",
+                "cluster/caps": "ๆฉŸ่ƒฝ",
+                "cluster/cfg/insecure": "ไป–ใฎใƒŽใƒผใƒ‰ใฎ่‡ชๅทฑ็ฝฒๅ่จผๆ˜Žๆ›ธใ‚’ๅ—ใ‘ๅ…ฅใ‚Œใ‚‹๏ผˆLAN ใฎใฟ๏ผ‰",
+                "cluster/cfg/name": "ใƒŽใƒผใƒ‰ๅ",
+                "cluster/cfg/test": "ใƒ†ใ‚นใƒˆ",
+                "cluster/cfg/tunnel": "ใƒˆใƒณใƒใƒซ็ตŒ็”ฑ",
+                "cluster/cfg/url": "ๅ…ฌ้–‹ URL๏ผˆไป–ใฎใƒŽใƒผใƒ‰ใŒใ“ใฎใƒŽใƒผใƒ‰ใซๆŽฅ็ถšใ™ใ‚‹ๆ–นๆณ•๏ผ‰",
+                "cluster/cfg/urlhint": "ใ“ใฎใƒŽใƒผใƒ‰ใซๅ…ฌ้–‹ใ‚ขใƒ‰ใƒฌใ‚นใŒใชใ„ๅ ดๅˆใฏ็ฉบๆฌ„ใฎใพใพใซใ—ใฆใใ ใ•ใ„ใ€‚ๅˆฐ้”ๅฏ่ƒฝใชใƒกใƒณใƒใƒผใธใฎใƒˆใƒณใƒใƒซใ‚’็ถญๆŒใ—ใ€Cloudflare ็ตŒ็”ฑใงใ‚‚ๅ‹•ไฝœใ—ใพใ™ใ€‚",
+                "cluster/clusterid": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผ ID",
+                "cluster/confirmleave": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใ‹ใ‚‰้›ข่„ฑใ—ใพใ™ใ‹๏ผŸไป–ใฎใƒŽใƒผใƒ‰ไธŠใฎใƒ•ใ‚กใ‚คใƒซใจใ‚ธใƒงใƒ–ใซใฏใ“ใ“ใ‹ใ‚‰ใ‚ขใ‚ฏใ‚ปใ‚นใงใใชใใชใ‚Šใพใ™ใ€‚",
+                "cluster/confirmremove": "ใƒŽใƒผใƒ‰ใ€Œ{0}ใ€ใ‚’ๅ‰Š้™คใ—ใพใ™ใ‹๏ผŸ",
+                "cluster/copied": "ใƒˆใƒผใ‚ฏใƒณใ‚’ใ‚ฏใƒชใƒƒใƒ—ใƒœใƒผใƒ‰ใซใ‚ณใƒ”ใƒผใ—ใพใ—ใŸ",
+                "cluster/copy": "ใ‚ณใƒ”ใƒผ",
+                "cluster/coresvalue": "{0} ใ‚ณใ‚ข",
+                "cluster/create": "ๆ–ฐใ—ใ„ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใ‚’ไฝœๆˆ",
+                "cluster/createbtn": "ไฝœๆˆ",
+                "cluster/created": "ไฝœๆˆๆ—ฅๆ™‚",
+                "cluster/createhint": "ใ“ใฎใƒŽใƒผใƒ‰ใŒๆœ€ๅˆใฎใƒกใƒณใƒใƒผใซใชใ‚Šใพใ™ใ€‚ใใฎๅพŒใ“ใ“ใงๅ‚ๅŠ ใƒˆใƒผใ‚ฏใƒณใ‚’็”Ÿๆˆใ—ใฆใƒŽใƒผใƒ‰ใ‚’่ฟฝๅŠ ใ—ใพใ™ใ€‚",
+                "cluster/direct": "็›ดๆŽฅ",
+                "cluster/disk": "ใƒ‡ใ‚ฃใ‚นใ‚ฏ",
+                "cluster/diskfree": "็ฉบใ {0} / {1}",
+                "cluster/drain": "ใƒ‰ใƒฌใ‚คใƒณ",
+                "cluster/entername": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผๅใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„",
+                "cluster/entertoken": "ๅ‚ๅŠ ใƒˆใƒผใ‚ฏใƒณใ‚’่ฒผใ‚Šไป˜ใ‘ใฆใใ ใ•ใ„",
+                "cluster/enterurl": "ใƒ†ใ‚นใƒˆใ™ใ‚‹ URL ใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„",
+                "cluster/expiresat": "{0} ใซๆœŸ้™ๅˆ‡ใ‚Œ",
+                "cluster/folder": "ใƒ•ใ‚ฉใƒซใƒ€ใƒผ",
+                "cluster/freeamount": "็ฉบใ {0}",
+                "cluster/gentoken": "ๅ‚ๅŠ ใƒˆใƒผใ‚ฏใƒณใ‚’็”Ÿๆˆ",
+                "cluster/id/accounts": "ใ‚ขใ‚ซใ‚ฆใƒณใƒˆ",
+                "cluster/id/apply": "้ฉ็”จ",
+                "cluster/id/conflicts": "ใƒญใƒผใ‚ซใƒซใฎใฟ",
+                "cluster/id/conflictswhy": "ๅŒใ˜ๅๅ‰ใฎใƒญใƒผใ‚ซใƒซใ‚ขใ‚ซใ‚ฆใƒณใƒˆใฏใใฎใพใพ็ถญๆŒใ•ใ‚Œใพใ™",
+                "cluster/id/disabled": "็„กๅŠน๏ผˆๅ„ใƒŽใƒผใƒ‰ใŒ่‡ช่บซใฎใ‚ขใ‚ซใ‚ฆใƒณใƒˆใง็ขบ่ช๏ผ‰",
+                "cluster/id/fallback": "ID ใ‚ชใƒชใ‚ธใƒณใซๅˆฐ้”ใงใใชใ„ใŸใ‚ใ€ใƒญใƒผใ‚ซใƒซใ‚ขใ‚ซใ‚ฆใƒณใƒˆใ‚’ไฝฟ็”จใ—ใพใ—ใŸ",
+                "cluster/id/forward": "่ปข้€",
+                "cluster/id/hint": "ID ใ‚ชใƒชใ‚ธใƒณใฏใ‚ฏใƒฉใ‚นใ‚ฟใƒผๅ†…ใฎใ™ในใฆใฎใƒญใ‚ฐใ‚คใƒณใ‚’ๆคœ่จผใ—ใพใ™ใ€‚ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใฏไป–ใฎใƒŽใƒผใƒ‰ใซใ‚ณใƒ”ใƒผใ•ใ‚Œใ‚‹ใŸใ‚ใ€ใ‚ชใƒชใ‚ธใƒณใซๅˆฐ้”ใงใใชใใฆใ‚‚ๅˆฉ็”จใงใใพใ™ใ€‚ใ‚ฐใƒซใƒผใƒ—ใฏๅๅ‰ใง็…งๅˆใ•ใ‚Œใ‚‹ใฎใงใ€ใ™ในใฆใฎใƒŽใƒผใƒ‰ใซๅŒใ˜ๆจฉ้™ใ‚ฐใƒซใƒผใƒ—ใ‚’ไฝœๆˆใ—ใฆใใ ใ•ใ„ใ€‚",
+                "cluster/id/lastlogin": "ๅ‰ๅ›žใฎใƒญใ‚ฐใ‚คใƒณ็ขบ่ช",
+                "cluster/id/lastsync": "ๅ‰ๅ›žใฎๅŒๆœŸ",
+                "cluster/id/local": "ๅ„ใƒŽใƒผใƒ‰ใŒ่‡ช่บซใฎใ‚ขใ‚ซใ‚ฆใƒณใƒˆใงใƒญใ‚ฐใ‚คใƒณใ‚’็ขบ่ชใ—ใพใ™",
+                "cluster/id/mirrors": "ใƒญใƒผใ‚ซใƒซใ‚ขใ‚ซใ‚ฆใƒณใƒˆ {1} ไปถไธญ {0} ไปถใŒใ‚ชใƒชใ‚ธใƒณใ‚’ๅๆ˜ ใ—ใฆใ„ใพใ™",
+                "cluster/id/mode": "ใƒขใƒผใƒ‰",
+                "cluster/id/origin": "ใ‚ชใƒชใ‚ธใƒณ",
+                "cluster/id/originhere": "ใ“ใฎใƒŽใƒผใƒ‰ใŒใ‚ฏใƒฉใ‚นใ‚ฟใƒผๅ…จไฝ“ใฎใƒญใ‚ฐใ‚คใƒณใ‚’็ขบ่ชใ—ใพใ™",
+                "cluster/id/published": "{0} ไปถใ‚’ใƒกใƒณใƒใƒผใซๅ…ฌ้–‹",
+                "cluster/id/replicated": "่ค‡่ฃฝๆธˆใฟ",
+                "cluster/id/skipped": "ใ‚นใ‚ญใƒƒใƒ—",
+                "cluster/id/skippedwhy": "ใ“ใฎใƒŽใƒผใƒ‰ใซใฏๆ‰€ๅฑžใ‚ฐใƒซใƒผใƒ—ใŒใฒใจใคใ‚‚ใ‚ใ‚Šใพใ›ใ‚“",
+                "cluster/id/sync": "ไปŠใ™ใๅŒๆœŸ",
+                "cluster/id/synced": "ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใ‚’ๅŒๆœŸใ—ใพใ—ใŸ",
+                "cluster/id/title": "ID๏ผˆใ‚ทใƒณใ‚ฐใƒซใ‚ตใ‚คใƒณใ‚ชใƒณ๏ผ‰",
+                "cluster/id/verifiedby": "ใƒญใ‚ฐใ‚คใƒณใฏ {0} ใŒ็ขบ่ช",
+                "cluster/info/novolume": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใซใพใ ใ‚นใƒˆใƒฌใƒผใ‚ธใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚",
+                "cluster/info/novolumehint": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผ่จญๅฎšใงใƒœใƒชใƒฅใƒผใƒ ใ‚’่ฟฝๅŠ ใ™ใ‚‹ใจใ€ใƒ•ใ‚กใ‚คใƒซใƒžใƒใƒผใ‚ธใƒฃใƒผใซ cluster:/ ใƒ‰ใƒฉใ‚คใƒ–ใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚",
+                "cluster/info/standalone": "ใ“ใฎใƒŽใƒผใƒ‰ใฏใ‚ฏใƒฉใ‚นใ‚ฟใƒผใซๅ‚ๅŠ ใ—ใฆใ„ใพใ›ใ‚“ใ€‚",
+                "cluster/info/standalonehint": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผ่จญๅฎšใงใ‚ฏใƒฉใ‚นใ‚ฟใƒผใ‚’ไฝœๆˆใพใŸใฏๅ‚ๅŠ ใ—ใฆใใ ใ•ใ„ใ€‚ใใ‚Œใพใง cluster:/ ใƒ‰ใƒฉใ‚คใƒ–ใฏ่กจ็คบใ•ใ‚Œใพใ›ใ‚“ใ€‚",
+                "cluster/info/tagline": "ใ“ใฎใƒŽใƒผใƒ‰ใ€ใƒ”ใ‚ขใ€ๅๅ‰็ฉบ้–“ใ€ใ‚นใ‚ฑใ‚ธใƒฅใƒผใƒฉใƒผใฎ็Šถๆ…‹",
+                "cluster/info/title": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผๆƒ…ๅ ฑ",
+                "cluster/invite": "ใƒŽใƒผใƒ‰ใ‚’่ฟฝๅŠ ",
+                "cluster/jobs/args": "ๅผ•ๆ•ฐ๏ผˆJSONใ€ไปปๆ„๏ผ‰",
+                "cluster/jobs/attemptn": "{0} ๅ›ž็›ฎใฎ่ฉฆ่กŒ",
+                "cluster/jobs/browse": "ๅ‚็…ง",
+                "cluster/jobs/cancel": "ใ‚ญใƒฃใƒณใ‚ปใƒซ",
+                "cluster/jobs/complete": "{0} / {1} ๅฎŒไบ†",
+                "cluster/jobs/dataset": "ใƒ‡ใƒผใ‚ฟใ‚ปใƒƒใƒˆ",
+                "cluster/jobs/datasethint": "ใƒ‡ใƒผใ‚ฟใ‚ปใƒƒใƒˆใ‚’ไฝฟใ†ๅ ดๅˆใ€ใ‚นใ‚ฏใƒชใƒ—ใƒˆใฏ mapper(files, emit) ใจ reducer(key, values) ใ‚’ๅฎš็พฉใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚ใƒ•ใ‚กใ‚คใƒซใฏใใ‚Œใ‚’ไฟๆŒใ™ใ‚‹ใƒŽใƒผใƒ‰ใงๅ‡ฆ็†ใ•ใ‚Œใพใ™ใ€‚",
+                "cluster/jobs/datasetlabel": "Map/reduce ใƒ‡ใƒผใ‚ฟใ‚ปใƒƒใƒˆ๏ผˆไปปๆ„ใฎ globใ€‚ๆŒ‡ๅฎšใ™ใ‚‹ใจใ“ใฎใ‚ธใƒงใƒ–ใŒ map/reduce ใ‚ธใƒงใƒ–ใซใชใ‚Šใพใ™๏ผ‰",
+                "cluster/jobs/detail": "ใ‚ธใƒงใƒ–ใฎ่ฉณ็ดฐ",
+                "cluster/jobs/duration": "ๆ‰€่ฆๆ™‚้–“",
+                "cluster/jobs/failedn": "{0} ไปถๅคฑๆ•—",
+                "cluster/jobs/features": "ๅฟ…่ฆใชๆฉŸ่ƒฝ",
+                "cluster/jobs/inputs": "ๅ…ฅๅŠ›ใƒ‘ใ‚น๏ผˆใ‚ซใƒณใƒžๅŒบๅˆ‡ใ‚Šใ€ใƒ‡ใƒผใ‚ฟใฎ่ฟ‘ใใงๅฎŸ่กŒใ™ใ‚‹ใŸใ‚ใซไฝฟ็”จ๏ผ‰",
+                "cluster/jobs/job": "ใ‚ธใƒงใƒ–",
+                "cluster/jobs/jobs": "ใ‚ธใƒงใƒ–",
+                "cluster/jobs/kind/map": "map ใ‚ฟใ‚นใ‚ฏ",
+                "cluster/jobs/kind/mapreduce": "map/reduce",
+                "cluster/jobs/kind/reduce": "reduce ใ‚ฟใ‚นใ‚ฏ",
+                "cluster/jobs/log": "ใƒญใ‚ฐ",
+                "cluster/jobs/minsecs": "{0} ๅˆ† {1} ็ง’",
+                "cluster/jobs/name": "ๅๅ‰",
+                "cluster/jobs/needscript": "ใ‚นใ‚ฏใƒชใƒ—ใƒˆใฎใƒ‘ใ‚นใŒๅฟ…่ฆใงใ™",
+                "cluster/jobs/node": "ใƒŽใƒผใƒ‰",
+                "cluster/jobs/nodetail": "ใ‚ธใƒงใƒ–ใ‚’้ธๆŠžใ™ใ‚‹ใจใƒญใ‚ฐใจๅ‡บๅŠ›ใŒ่กจ็คบใ•ใ‚Œใพใ™",
+                "cluster/jobs/none": "ใ‚ธใƒงใƒ–ใฏใพใ ใ‚ใ‚Šใพใ›ใ‚“",
+                "cluster/jobs/output": "ๅ‡บๅŠ›",
+                "cluster/jobs/owner": "ๆ‰€ๆœ‰่€…",
+                "cluster/jobs/progress": "้€ฒๆ—",
+                "cluster/jobs/queued": "ใ‚ธใƒงใƒ–ใ‚’ใ‚ญใƒฅใƒผใซ่ฟฝๅŠ ใ—ใพใ—ใŸ",
+                "cluster/jobs/run": "ใ‚ธใƒงใƒ–ใ‚’ๅฎŸ่กŒ",
+                "cluster/jobs/scheduler": "ใ‚นใ‚ฑใ‚ธใƒฅใƒผใƒฉใƒผ: {0}",
+                "cluster/jobs/script": "ใ‚นใ‚ฏใƒชใƒ—ใƒˆ",
+                "cluster/jobs/scriptlabel": "ใ‚นใ‚ฏใƒชใƒ—ใƒˆ๏ผˆrun(job) ใ‚’ๅฎš็พฉใ™ใ‚‹ .agi ใƒ•ใ‚กใ‚คใƒซ๏ผ‰",
+                "cluster/jobs/secs": "{0} ็ง’",
+                "cluster/jobs/slots": "ใ“ใฎใƒŽใƒผใƒ‰ใง {0}/{1} ใ‚นใƒญใƒƒใƒˆไฝฟ็”จไธญ",
+                "cluster/jobs/status": "็Šถๆ…‹",
+                "cluster/jobs/submit": "ใ‚ธใƒงใƒ–ใ‚’ๆŠ•ๅ…ฅ",
+                "cluster/jobs/submithint": "ใ‚นใ‚ฏใƒชใƒ—ใƒˆใฏ้ธใฐใ‚ŒใŸใƒŽใƒผใƒ‰ไธŠใงใ‚ใชใŸใฎๆจฉ้™ใงๅฎŸ่กŒใ•ใ‚Œใพใ™ใ€‚cluster:/ ใƒ‘ใ‚นใฏ้€šๅธธใฉใŠใ‚Šไฝฟใˆใพใ™ใ€‚",
+                "cluster/jobs/summary": "ๅฎŸ่กŒไธญ {0}ใ€ๅพ…ๆฉŸไธญ {1}ใ€ๅฎŒไบ† {2}",
+                "cluster/jobs/tagline": "ๆœ€้ฉใชใƒŽใƒผใƒ‰ใงใ‚นใ‚ฏใƒชใƒ—ใƒˆใ‚’ๅฎŸ่กŒ",
+                "cluster/jobs/tasks": "ใ‚ฟใ‚นใ‚ฏ",
+                "cluster/jobs/timeout": "ใ‚ฟใ‚คใƒ ใ‚ขใ‚ฆใƒˆ๏ผˆ็ง’๏ผ‰",
+                "cluster/jobs/title": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใ‚ธใƒงใƒ–",
+                "cluster/jobs/typepath": "ใ‚นใ‚ฏใƒชใƒ—ใƒˆใฎใƒ‘ใ‚นใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„๏ผˆไพ‹: user:/jobs/index.agi๏ผ‰",
+                "cluster/jobs/waiting": "ๅพ…ๆฉŸ็†็”ฑ",
+                "cluster/join": "ๆ—ขๅญ˜ใฎใ‚ฏใƒฉใ‚นใ‚ฟใƒผใซๅ‚ๅŠ ",
+                "cluster/joinbtn": "ๅ‚ๅŠ ",
+                "cluster/joinhint": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใฎๅˆฐ้”ๅฏ่ƒฝใชใƒกใƒณใƒใƒผใง็”Ÿๆˆใ—ใŸๅ‚ๅŠ ใƒˆใƒผใ‚ฏใƒณใ‚’่ฒผใ‚Šไป˜ใ‘ใฆใใ ใ•ใ„ใ€‚",
+                "cluster/joining": "ๅ‚ๅŠ ใ—ใฆใ„ใพใ™...",
+                "cluster/justnow": "ใŸใฃใŸไปŠ",
+                "cluster/key": "ใƒŽใƒผใƒ‰ใ‚ญใƒผ",
+                "cluster/lastseen": "ๆœ€็ต‚็ขบ่ช",
+                "cluster/leave": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใ‹ใ‚‰้›ข่„ฑ",
+                "cluster/link/info": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผๆƒ…ๅ ฑ",
+                "cluster/link/jobs": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใ‚ธใƒงใƒ–",
+                "cluster/link/settings": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผ่จญๅฎš",
+                "cluster/load": "่ฒ ่ท",
+                "cluster/maintenance": "ใƒกใƒณใƒ†ใƒŠใƒณใ‚น",
+                "cluster/members": "ใƒกใƒณใƒใƒผ",
+                "cluster/membershint": "ใƒกใƒณใƒ†ใƒŠใƒณใ‚นใฏใƒŽใƒผใƒ‰ใ‚’ใ‚นใ‚ฑใ‚ธใƒฅใƒผใƒชใƒณใ‚ฐใ‹ใ‚‰ๅค–ใ—ใพใ™ใ€‚ใƒ‰ใƒฌใ‚คใƒณใฏใ•ใ‚‰ใซใ‚ณใƒ”ใƒผใ‚’ไป–ใฎใƒŽใƒผใƒ‰ใธ็งปใ™ใŸใ‚ใ€ๅฎ‰ๅ…จใซๅœๆญขใงใใพใ™ใ€‚",
+                "cluster/meta/entries": "ใ‚จใƒณใƒˆใƒชใƒผ",
+                "cluster/meta/entriesvalue": "ใƒ•ใ‚กใ‚คใƒซ {0}ใ€ใƒ•ใ‚ฉใƒซใƒ€ใƒผ {1}ใ€ใƒœใƒชใƒฅใƒผใƒ  {2}",
+                "cluster/meta/leader": "ใƒชใƒผใƒ€ใƒผ",
+                "cluster/meta/log": "ใƒญใ‚ฐ",
+                "cluster/meta/noleader": "้ธๅ‡บไธญ",
+                "cluster/meta/pending": "ไฟ็•™ไธญ {0}",
+                "cluster/meta/term": "ไปปๆœŸ {0}",
+                "cluster/natonly": "URL ใชใ—",
+                "cluster/never": "ใชใ—",
+                "cluster/node": "ใƒŽใƒผใƒ‰",
+                "cluster/nodes": "ใƒŽใƒผใƒ‰",
+                "cluster/nonodes": "ใƒŽใƒผใƒ‰ใŒใ‚ใ‚Šใพใ›ใ‚“",
+                "cluster/noreport": "ใพใ ๅ ฑๅ‘ŠใŒใ‚ใ‚Šใพใ›ใ‚“",
+                "cluster/noroute": "็ตŒ่ทฏใชใ—",
+                "cluster/notmember": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใซๅ‚ๅŠ ใ—ใฆใ„ใพใ›ใ‚“",
+                "cluster/notunnel": "ใƒˆใƒณใƒใƒซใชใ—",
+                "cluster/now": "็พๅœจ",
+                "cluster/platform": "ใƒ—ใƒฉใƒƒใƒˆใƒ•ใ‚ฉใƒผใƒ ",
+                "cluster/platformline": "{0} / {1}ใ€{2} ใ‚ณใ‚ข",
+                "cluster/pol/1": "ใ‚ณใƒ”ใƒผ 1 ๅ€‹",
+                "cluster/pol/2": "ใ‚ณใƒ”ใƒผ 2 ๅ€‹",
+                "cluster/pol/3": "ใ‚ณใƒ”ใƒผ 3 ๅ€‹",
+                "cluster/pol/4": "ใ‚ณใƒ”ใƒผ 4 ๅ€‹",
+                "cluster/pol/copies": "ใ‚ณใƒ”ใƒผ {0} ๅ€‹",
+                "cluster/pol/hint": "ใƒใƒชใ‚ทใƒผใฎใชใ„ใƒ•ใ‚ฉใƒซใƒ€ใƒผใฏใ‚ณใƒ”ใƒผใ‚’ 1 ใคใ ใ‘ไฟๆŒใ—ใพใ™ใ€‚่ฟฝๅŠ ใฎใ‚ณใƒ”ใƒผใฏใƒฌใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใ‚ตใƒผใƒ“ใ‚นใŒไฝœๆˆใ—ใพใ™ใ€‚",
+                "cluster/pol/label": "ๆœ€ไธŠไฝใƒ•ใ‚ฉใƒซใƒ€ใƒผใ”ใจใฎใ‚ณใƒ”ใƒผๆ•ฐ",
+                "cluster/pol/set": "่จญๅฎš",
+                "cluster/pol/title": "ใ‚ณใƒ”ใƒผใƒใƒชใ‚ทใƒผ",
+                "cluster/probe": "็–Ž้€š็ขบ่ช",
+                "cluster/probefail": "{0}: {1}๏ผˆ{2}๏ผ‰",
+                "cluster/probeok": "{0}: {1} ms๏ผˆ{2}๏ผ‰",
+                "cluster/reach": "ๅˆฐ้”ๆ€ง",
+                "cluster/reachableok": "ๅˆฐ้”ๅฏ่ƒฝใ€ๅพ€ๅพฉ {0} ms",
+                "cluster/redetect": "ๅ†ๆคœๅ‡บ",
+                "cluster/remove": "ๅ‰Š้™ค",
+                "cluster/repl/evacuating": "{0} ๅ€‹ใฎใƒœใƒชใƒฅใƒผใƒ ใ‚’้€€้ฟไธญ",
+                "cluster/repl/files": "ใƒ•ใ‚กใ‚คใƒซ",
+                "cluster/repl/fromto": "{0} ใ‹ใ‚‰ {1}",
+                "cluster/repl/healthy": "ใ™ในใฆใฎใƒ•ใ‚กใ‚คใƒซใŒใƒใƒชใ‚ทใƒผใ‚’ๆบ€ใŸใ—ใฆใ„ใพใ™",
+                "cluster/repl/inflight": "ๅ‡ฆ็†ไธญ",
+                "cluster/repl/inflightvalue": "ๅฎŸ่กŒไธญ {0}ใ€ๅพ…ๆฉŸไธญ {1}",
+                "cluster/repl/inspect": "ใƒ•ใ‚กใ‚คใƒซใ‚’่ชฟในใ‚‹",
+                "cluster/repl/inspectbtn": "ใ‚ณใƒ”ใƒผใ‚’่กจ็คบ",
+                "cluster/repl/issues": "ๅ•้กŒ {0} ไปถ",
+                "cluster/repl/lastrun": "ๅ‰ๅ›žๅฎŸ่กŒ {0}",
+                "cluster/repl/nocopy": "ๆญฃๅธธใชใ‚ณใƒ”ใƒผใชใ—",
+                "cluster/repl/nolocations": "่จ˜้Œฒใ•ใ‚ŒใŸใ‚ณใƒ”ใƒผใฏใ‚ใ‚Šใพใ›ใ‚“",
+                "cluster/repl/notasks": "ใƒฌใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใ‚ฟใ‚นใ‚ฏใฏใ‚ใ‚Šใพใ›ใ‚“",
+                "cluster/repl/notfound": "ๅๅ‰็ฉบ้–“ใซใ“ใฎใƒ‘ใ‚นใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“",
+                "cluster/repl/over": "ไฝ™ๅˆ†ใชใ‚ณใƒ”ใƒผ",
+                "cluster/repl/plan": "ไปŠใ™ใไฟฎๅพฉ",
+                "cluster/repl/planned": "ใƒ—ใƒฉใƒณใƒŠใƒผใ‚’ๅฎŸ่กŒ: ใ‚ญใƒฅใƒผ {0}ใ€ๅ‰Š้™ค {1}ใ€ๆœŸ้™ๅˆ‡ใ‚Œ {2}",
+                "cluster/repl/planner": "ใƒ—ใƒฉใƒณใƒŠใƒผ",
+                "cluster/repl/primary": "ใƒ—ใƒฉใ‚คใƒžใƒช",
+                "cluster/repl/reason/evacuate": "้€€้ฟ",
+                "cluster/repl/reason/repair": "ไฟฎๅพฉ",
+                "cluster/repl/reason/replica": "ๆ–ฐใ—ใ„ใ‚ณใƒ”ใƒผ",
+                "cluster/repl/route": "ๅ…ƒ / ๅ…ˆ",
+                "cluster/repl/stale": "ๅคใ„ใ‚ณใƒ”ใƒผ",
+                "cluster/repl/task": "ใ‚ฟใ‚นใ‚ฏ",
+                "cluster/repl/title": "ใƒฌใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณ",
+                "cluster/repl/under": "ใ‚ณใƒ”ใƒผไธ่ถณ",
+                "cluster/repl/verified": "{0} ๅ€‹ใฎใ‚ณใƒ”ใƒผ๏ผˆ{1}๏ผ‰ใ‚’ๆคœ่จผใ€ๅคใ„ใ‚‚ใฎ {2}",
+                "cluster/repl/verify": "ใ“ใฎใƒŽใƒผใƒ‰ใ‚’ๆคœ่จผ",
+                "cluster/requestfailed": "ใƒชใ‚ฏใ‚จใ‚นใƒˆใซๅคฑๆ•—ใ—ใพใ—ใŸ",
+                "cluster/resume": "ๅ†้–‹",
+                "cluster/revoke": "ๅ–ใ‚Šๆถˆใ™",
+                "cluster/route": "็ตŒ่ทฏ",
+                "cluster/route/direct": "็›ดๆŽฅ",
+                "cluster/route/none": "็ตŒ่ทฏใชใ—",
+                "cluster/route/relay": "{0} ็ตŒ็”ฑใงไธญ็ถ™",
+                "cluster/route/tunnel": "ใƒˆใƒณใƒใƒซ",
+                "cluster/save": "ไฟๅญ˜",
+                "cluster/saved": "ไฟๅญ˜ใ—ใพใ—ใŸ",
+                "cluster/sched/cpu": "CPU ็ฉบใ",
+                "cluster/sched/cpu/hint": "็ฉบใ„ใฆใ„ใ‚‹ใƒ—ใƒญใ‚ปใƒƒใ‚ตใƒผใ‚’ๅ„ชๅ…ˆ",
+                "cluster/sched/degraded": "ๅฅๅ…จๆ€ง",
+                "cluster/sched/degraded/hint": "่ฒ ่ทใฎ้ซ˜ใ„ใƒŽใƒผใƒ‰ใ‚’้ฟใ‘ใ‚‹",
+                "cluster/sched/disk": "็ฉบใใƒ‡ใ‚ฃใ‚นใ‚ฏ",
+                "cluster/sched/disk/hint": "ๅฎน้‡ใซไฝ™่ฃ•ใฎใ‚ใ‚‹ใƒŽใƒผใƒ‰ใ‚’ๅ„ชๅ…ˆ",
+                "cluster/sched/diskfree": "ใƒ‡ใ‚ฃใ‚นใ‚ฏ็ฉบใ",
+                "cluster/sched/diversity": "ใ‚ตใ‚คใƒˆๅˆ†ๆ•ฃ",
+                "cluster/sched/diversity/hint": "2 ใค็›ฎใฎใ‚ณใƒ”ใƒผใ‚’ 1 ใค็›ฎใ‹ใ‚‰้›ขใ‚ŒใŸๅ ดๆ‰€ใซ็ฝฎใใ€1 ใคใฎใ‚ตใ‚คใƒˆใŒๆญขใพใฃใฆใ‚‚ใƒ‡ใƒผใ‚ฟใ‚’ๅคฑใ‚ใชใ„ใ‚ˆใ†ใซใ™ใ‚‹",
+                "cluster/sched/explain": "ใ‚ธใƒงใƒ–ใŒใใ“ใซๅ‰ฒใ‚Šๅฝ“ใฆใ‚‰ใ‚ŒใŸ็†็”ฑใฏ๏ผŸ",
+                "cluster/sched/explainbtn": "่ชฌๆ˜Ž",
+                "cluster/sched/explainhint": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใ‚ธใƒงใƒ–ใƒšใƒผใ‚ธใ‹ใ‚‰ใ‚ธใƒงใƒ– ID ใ‚’ใ‚ณใƒ”ใƒผใ™ใ‚‹ใจใ€ๅ‰ฒใ‚Šๅฝ“ใฆๆ™‚ใฎ้ †ไฝใ‚’็ขบ่ชใงใใพใ™ใ€‚",
+                "cluster/sched/factor/cpu free": "CPU ็ฉบใ",
+                "cluster/sched/factor/degraded": "ไฝŽไธ‹",
+                "cluster/sched/factor/disk free": "ใƒ‡ใ‚ฃใ‚นใ‚ฏ็ฉบใ",
+                "cluster/sched/factor/locality": "ใƒ‡ใƒผใ‚ฟใฎๅ ดๆ‰€",
+                "cluster/sched/factor/memory free": "ใƒกใƒขใƒช็ฉบใ",
+                "cluster/sched/factor/network distance": "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏ่ท้›ข",
+                "cluster/sched/factor/preferred features": "ๅธŒๆœ›ใ™ใ‚‹ๆฉŸ่ƒฝ",
+                "cluster/sched/factor/queue depth": "ใ‚ญใƒฅใƒผใฎ้•ทใ•",
+                "cluster/sched/factor/site diversity": "ใ‚ตใ‚คใƒˆๅˆ†ๆ•ฃ",
+                "cluster/sched/features": "ๆฉŸ่ƒฝ {0}",
+                "cluster/sched/from": "ๅ…ƒ / ๅ…ˆ",
+                "cluster/sched/here": "ใƒญใƒผใ‚ซใƒซ",
+                "cluster/sched/hint": "ใ‚ธใƒงใƒ–ใ€ๆ–ฐใ—ใ„ใƒ•ใ‚กใ‚คใƒซใ€่ฟฝๅŠ ใฎใ‚ณใƒ”ใƒผใฏๆœ€้ซ˜ใ‚นใ‚ณใ‚ขใฎใƒŽใƒผใƒ‰ใซ้€ใ‚‰ใ‚Œใพใ™ใ€‚้‡ใฟใ‚’ไธŠใ’ใ‚‹ใจใใฎ่ฆ็ด ใŒใ‚ˆใ‚Š้‡่ฆ–ใ•ใ‚Œใพใ™ใ€‚็ตๆžœใฎใ‚นใ‚ณใ‚ขใฏใ‚ฏใƒฉใ‚นใ‚ฟใƒผๆƒ…ๅ ฑใซ่กจ็คบใ•ใ‚Œใพใ™ใ€‚",
+                "cluster/sched/infohint": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผ่จญๅฎšใฎ้‡ใฟใซใ‚ˆใ‚‹ๅ„ใƒŽใƒผใƒ‰ใฎ็พๅœจใฎใ‚นใ‚ณใ‚ขใงใ™ใ€‚ใ‚ธใƒงใƒ–ใ€ๆ–ฐใ—ใ„ใƒ•ใ‚กใ‚คใƒซใ€่ฟฝๅŠ ใฎใ‚ณใƒ”ใƒผใฏๆœ€้ซ˜ใ‚นใ‚ณใ‚ขใซ้€ใ‚‰ใ‚Œใพใ™ใ€‚",
+                "cluster/sched/latency": "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏ่ท้›ข",
+                "cluster/sched/latency/hint": "ใƒ‡ใƒผใ‚ฟใ‹ใ‚‰้ ใ„ใƒŽใƒผใƒ‰ใ‚’้ฟใ‘ใ‚‹",
+                "cluster/sched/locality": "ใƒ‡ใƒผใ‚ฟใฎๅ ดๆ‰€",
+                "cluster/sched/locality/hint": "ๆ—ขใซใƒ‡ใƒผใ‚ฟใ‚’ๆŒใคใƒŽใƒผใƒ‰ใ‚’ๅ„ชๅ…ˆ",
+                "cluster/sched/matrixhint": "ใƒŽใƒผใƒ‰้–“ใฎๅพ€ๅพฉๆ™‚้–“๏ผˆใƒŸใƒช็ง’๏ผ‰ใ€‚่ท้›ขใฏ้€šๅธธๅˆฅใฎใ‚ตใ‚คใƒˆใ‚’ๆ„ๅ‘ณใ™ใ‚‹ใŸใ‚ใ€ใƒ•ใ‚กใ‚คใƒซใฎใ‚ณใƒ”ใƒผใฏๆœ€ใ‚‚้ ใ„ใƒšใ‚ขใซๅˆ†ๆ•ฃใ•ใ‚Œใพใ™ใ€‚",
+                "cluster/sched/mem": "ใƒกใƒขใƒช็ฉบใ",
+                "cluster/sched/memory": "็ฉบใใƒกใƒขใƒช",
+                "cluster/sched/memory/hint": "RAM ใซไฝ™่ฃ•ใฎใ‚ใ‚‹ใƒŽใƒผใƒ‰ใ‚’ๅ„ชๅ…ˆ",
+                "cluster/sched/next": "ๆฌกใฎใ‚ธใƒงใƒ–ใฏ {0} ใงๅฎŸ่กŒ",
+                "cluster/sched/nojob": "ใ“ใฎใ‚ฏใƒฉใ‚นใ‚ฟใƒผใซใใฎใ‚ธใƒงใƒ–ใฏใ‚ใ‚Šใพใ›ใ‚“",
+                "cluster/sched/nonodes": "ใ‚นใ‚ณใ‚ขใ‚’ไป˜ใ‘ใ‚‹ใƒŽใƒผใƒ‰ใŒใ‚ใ‚Šใพใ›ใ‚“",
+                "cluster/sched/ping": "ๅพ€ๅพฉๆ™‚้–“",
+                "cluster/sched/preferred": "ๅธŒๆœ›ใ™ใ‚‹ๆฉŸ่ƒฝ",
+                "cluster/sched/preferred/hint": "ใ‚ธใƒงใƒ–ใŒๆฑ‚ใ‚ใ‚‹ไปปๆ„ๆฉŸ่ƒฝใ‚’ๆŒใคใƒŽใƒผใƒ‰ใ‚’ๅ„ชๅ…ˆ",
+                "cluster/sched/queue": "ใ‚ญใƒฅใƒผใฎ้•ทใ•",
+                "cluster/sched/queue/hint": "ๆ—ขใซๅฟ™ใ—ใ„ใƒŽใƒผใƒ‰ใ‚’้ฟใ‘ใ‚‹",
+                "cluster/sched/reset": "ๆ—ขๅฎšๅ€คใซๆˆปใ™",
+                "cluster/sched/restored": "ๆ—ขๅฎšใฎ้‡ใฟใซๆˆปใ—ใพใ—ใŸ",
+                "cluster/sched/save": "้‡ใฟใ‚’ไฟๅญ˜",
+                "cluster/sched/saved": "้‡ใฟใ‚’ใ‚ฏใƒฉใ‚นใ‚ฟใƒผๅ…จไฝ“ใซไฟๅญ˜ใ—ใพใ—ใŸ",
+                "cluster/sched/score": "ใ‚นใ‚ณใ‚ข",
+                "cluster/sched/skipped": "ๅฏพ่ฑกๅค–",
+                "cluster/sched/title": "ใ‚นใ‚ฑใ‚ธใƒฅใƒผใƒชใƒณใ‚ฐ",
+                "cluster/sched/unmeasured": "ๆœช่จˆๆธฌ",
+                "cluster/settings/tagline": "ใ“ใฎใƒŽใƒผใƒ‰ใจใ€ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใฎใƒ•ใ‚กใ‚คใƒซไฟๅญ˜ใƒปใ‚ธใƒงใƒ–ๅฎŸ่กŒๆ–นๆณ•ใ‚’่จญๅฎšใ—ใพใ™",
+                "cluster/settings/title": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผ่จญๅฎš",
+                "cluster/state": "็Šถๆ…‹",
+                "cluster/state/cancelled": "ใ‚ญใƒฃใƒณใ‚ปใƒซๆธˆใฟ",
+                "cluster/state/committed": "ๆ›ธใ่พผใฟๆธˆใฟ",
+                "cluster/state/degraded": "ไฝŽไธ‹",
+                "cluster/state/done": "ๅฎŒไบ†",
+                "cluster/state/draining": "ใƒ‰ใƒฌใ‚คใƒณไธญ",
+                "cluster/state/failed": "ๅคฑๆ•—",
+                "cluster/state/maintenance": "ใƒกใƒณใƒ†ใƒŠใƒณใ‚นไธญ",
+                "cluster/state/offline": "ใ‚ชใƒ•ใƒฉใ‚คใƒณ",
+                "cluster/state/online": "ใ‚ชใƒณใƒฉใ‚คใƒณ",
+                "cluster/state/pending": "ไฟ็•™ไธญ",
+                "cluster/state/queued": "ๅพ…ๆฉŸไธญ",
+                "cluster/state/running": "ๅฎŸ่กŒไธญ",
+                "cluster/state/scheduled": "ๅ‰ฒใ‚Šๅฝ“ใฆๆธˆใฟ",
+                "cluster/state/stale": "ๅคใ„",
+                "cluster/state/succeeded": "ๆˆๅŠŸ",
+                "cluster/state/unknown": "ไธๆ˜Ž",
+                "cluster/state/verified": "ๆคœ่จผๆธˆใฟ",
+                "cluster/state/writing": "ๆ›ธใ่พผใฟไธญ",
+                "cluster/storage": "ใ‚นใƒˆใƒฌใƒผใ‚ธ",
+                "cluster/testing": "ใƒ†ใ‚นใƒˆไธญ...",
+                "cluster/thisnode": "ใ“ใฎใƒŽใƒผใƒ‰",
+                "cluster/tokenhint": "ใƒˆใƒผใ‚ฏใƒณใฏๅ…ฌ้–‹ URL ใ‚’ๆŒใคใƒŽใƒผใƒ‰ใงใฎใฟ็”Ÿๆˆใงใใพใ™ใ€‚ใ‚‚ใ†ไธ€ๆ–นใฎใƒŽใƒผใƒ‰ใฎใ‚ฏใƒฉใ‚นใ‚ฟใƒผ่จญๅฎšใซ่ฒผใ‚Šไป˜ใ‘ใฆใใ ใ•ใ„ใ€‚",
+                "cluster/tokenline": "{0} ใซๆœŸ้™ๅˆ‡ใ‚Œ ยท ไฝฟ็”จ {1} ๅ›ž",
+                "cluster/ttl/1h": "1 ๆ™‚้–“",
+                "cluster/ttl/24h": "24 ๆ™‚้–“",
+                "cluster/ttl/7d": "7 ๆ—ฅ",
+                "cluster/tunnel": "ใƒˆใƒณใƒใƒซ",
+                "cluster/tunnelhere": "ใ“ใฎใƒŽใƒผใƒ‰ใธใฎใƒˆใƒณใƒใƒซ",
+                "cluster/tunnelvia": "{0} ็ตŒ็”ฑใฎใƒˆใƒณใƒใƒซ",
+                "cluster/unreachable": "ๅˆฐ้”ไธ่ƒฝ",
+                "cluster/updated": "{0} ใซๆ›ดๆ–ฐ",
+                "cluster/uptime": "็จผๅƒๆ™‚้–“",
+                "cluster/uptimevalue": "{0} ๆ™‚้–“ {1} ๅˆ†",
+                "cluster/version": "ใƒใƒผใ‚ธใƒงใƒณ",
+                "cluster/via": "{0} ็ตŒ็”ฑ",
+                "cluster/vol/add": "ใ“ใฎใƒŽใƒผใƒ‰ใฎใƒ•ใ‚ฉใƒซใƒ€ใƒผใ‚’ๆไพ›",
+                "cluster/vol/addbtn": "ใƒœใƒชใƒฅใƒผใƒ ใ‚’่ฟฝๅŠ ",
+                "cluster/vol/added": "ใƒœใƒชใƒฅใƒผใƒ ใ‚’่ฟฝๅŠ ใ—ใพใ—ใŸ",
+                "cluster/vol/autoro": "็ฉบใๅฎน้‡ใŒๅฐ‘ใชใ„ใƒœใƒชใƒฅใƒผใƒ ใ‚’่‡ชๅ‹•็š„ใซ่ชญใฟๅ–ใ‚Šๅฐ‚็”จใซใ™ใ‚‹",
+                "cluster/vol/autorohint": "ใ™ในใฆใฎใƒŽใƒผใƒ‰ใซ้ฉ็”จใ•ใ‚Œใพใ™ใ€‚็ฉบใใŒ 5% ๆœชๆบ€ใฎใƒœใƒชใƒฅใƒผใƒ ใฏๆ–ฐใ—ใ„ใƒ•ใ‚กใ‚คใƒซใฎๅ—ใ‘ๅ…ฅใ‚Œใ‚’ๅœๆญขใ—ใ€7% ใ‚’่ถ…ใˆใ‚‹ใจๅ†้–‹ใ—ใพใ™ใ€‚ใ‚ชใƒ•ใซใ™ใ‚‹ใจใƒœใƒชใƒฅใƒผใƒ ใ‚’ๆบ€ๆฏใพใงไฝฟใˆใพใ™ใ€‚ใ“ใ‚Œใซใ‚ˆใ‚Šใƒญใƒƒใ‚ฏใ•ใ‚Œใฆใ„ใŸใƒœใƒชใƒฅใƒผใƒ ใฏ 1 ๅˆ†ไปฅๅ†…ใซๆ›ธใ่พผใฟๅฏ่ƒฝใซๆˆปใ‚Šใพใ™ใ€‚",
+                "cluster/vol/autorooff": "ใƒœใƒชใƒฅใƒผใƒ ใ‚’ๆบ€ๆฏใพใงไฝฟใˆใ‚‹ใ‚ˆใ†ใซใชใ‚Šใพใ—ใŸ",
+                "cluster/vol/autoroon": "็ฉบใๅฎน้‡ใŒๅฐ‘ใชใ„ใƒœใƒชใƒฅใƒผใƒ ใฏ่ชญใฟๅ–ใ‚Šๅฐ‚็”จใซใชใ‚Šใพใ™",
+                "cluster/vol/confirmevac": "ใ€Œ{0}ใ€ใ‹ใ‚‰ใ™ในใฆใฎใƒ•ใ‚กใ‚คใƒซใ‚’็งปๅ‹•ใ—ใพใ™ใ‹๏ผŸใƒœใƒชใƒฅใƒผใƒ ใฏ่ชญใฟๅ–ใ‚Šๅฐ‚็”จใซใชใ‚Šใ€ใ™ในใฆใฎใ‚ณใƒ”ใƒผใŒไป–ๆ‰€ใซๆƒใฃใŸๆ™‚็‚นใงๅปƒๆญขใ•ใ‚Œใพใ™ใ€‚",
+                "cluster/vol/confirmremove": "ใ€Œ{0}ใ€ใฎๆไพ›ใ‚’ๅœๆญขใ—ใพใ™ใ‹๏ผŸใƒ•ใ‚กใ‚คใƒซใฏใƒ‡ใ‚ฃใ‚นใ‚ฏใซๆฎ‹ใ‚Šใพใ™ใŒใ€ใใ“ใซใ—ใ‹ใชใ„ใ‚ณใƒ”ใƒผใฏไฝฟใˆใชใใชใ‚Šใพใ™ใ€‚",
+                "cluster/vol/evacstarted": "้€€้ฟใ‚’้–‹ๅง‹ใ—ใพใ—ใŸ: ็งปๅ‹•ใ™ใ‚‹ใƒ•ใ‚กใ‚คใƒซ {0}",
+                "cluster/vol/evacuate": "้€€้ฟ",
+                "cluster/vol/evacuating": "้€€้ฟไธญ",
+                "cluster/vol/files": "ใƒ•ใ‚กใ‚คใƒซ",
+                "cluster/vol/full": "็ฉบใไธ่ถณ",
+                "cluster/vol/hint": "cluster:/ ใซๆ›ธใ่พผใ‚“ใ ใƒ•ใ‚กใ‚คใƒซใฏใ€ๆไพ›ใ•ใ‚ŒใŸใƒ•ใ‚ฉใƒซใƒ€ใƒผๅ†…ใซ้€šๅธธใฎใƒ•ใ‚กใ‚คใƒซใจใ—ใฆไฟๅญ˜ใ•ใ‚Œใพใ™ใ€‚ใƒ•ใ‚ฉใƒซใƒ€ใƒผใซๆ—ขใซใ‚ใ‚‹ใƒ•ใ‚กใ‚คใƒซใฏๆฌกๅ›žใฎๅ†ใ‚นใ‚ญใƒฃใƒณใงๅๅ‰็ฉบ้–“ใซๅ–ใ‚Š่พผใพใ‚Œใพใ™ใ€‚ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใซใƒœใƒชใƒฅใƒผใƒ ใŒ 1 ใคไปฅไธŠใ‚ใ‚‹ใจใ€ใƒ•ใ‚กใ‚คใƒซใƒžใƒใƒผใ‚ธใƒฃใƒผใซ cluster:/ ใƒ‰ใƒฉใ‚คใƒ–ใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚",
+                "cluster/vol/none": "ใƒœใƒชใƒฅใƒผใƒ ใฏใพใ ใ‚ใ‚Šใพใ›ใ‚“ใ€‚ใ“ใฎใƒŽใƒผใƒ‰ใฎใƒ•ใ‚ฉใƒซใƒ€ใƒผใ‚’่ฟฝๅŠ ใ™ใ‚‹ใจ cluster:/ ใซใƒ•ใ‚กใ‚คใƒซใ‚’ไฟๅญ˜ใงใใพใ™",
+                "cluster/vol/readonly": "่ชญใฟๅ–ใ‚Šๅฐ‚็”จ",
+                "cluster/vol/rescan": "ใ“ใฎใƒŽใƒผใƒ‰ใ‚’ๅ†ใ‚นใ‚ญใƒฃใƒณ",
+                "cluster/vol/rescanned": "ๅ†ใ‚นใ‚ญใƒฃใƒณๅฎŒไบ†: ๅ–ใ‚Š่พผใฟ {0}ใ€ๅคใ„ใ‚‚ใฎ {1}",
+                "cluster/vol/ro": "่ชญใฟๅ–ใ‚Šๅฐ‚็”จ",
+                "cluster/vol/space": "็ฉบใ / ๅˆ่จˆ",
+                "cluster/vol/summary": "ใƒœใƒชใƒฅใƒผใƒ  {0}ใ€็ฉบใ {1}",
+                "cluster/vol/volume": "ใƒœใƒชใƒฅใƒผใƒ ",
+                "cluster/vol/writable": "ๆ›ธใ่พผใฟๅฏ่ƒฝใซใ™ใ‚‹",
+                "cluster/waitinghost": "ๅˆฐ้”ๅฏ่ƒฝใชใƒกใƒณใƒใƒผใ‚’ๅพ…ใฃใฆใ„ใพใ™",
+                "cluster/when": "ๆ—ฅๆ™‚",
+                "cluster/willtunnel": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผๅ‚ๅŠ ๅพŒใฏใƒกใƒณใƒใƒผ็ตŒ็”ฑใงใƒˆใƒณใƒใƒซใ—ใพใ™",
+                "cluster/you": "่‡ชๅˆ†",
+                "msg/AGI gateway not ready": "AGI ใ‚ฒใƒผใƒˆใ‚ฆใ‚งใ‚คใฎๆบ–ๅ‚™ใŒใงใใฆใ„ใพใ›ใ‚“",
+                "msg/Invalid MAC address": "MAC ใ‚ขใƒ‰ใƒฌใ‚นใŒ็„กๅŠนใงใ™",
+                "msg/POST required": "POST ใŒๅฟ…่ฆใงใ™",
+                "msg/URL must start with http:// or https://": "URL ใฏ http:// ใพใŸใฏ https:// ใงๅง‹ใพใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™",
+                "msg/a job id is required": "ใ‚ธใƒงใƒ– ID ใŒๅฟ…่ฆใงใ™",
+                "msg/a map/reduce job needs a dataset pattern": "map/reduce ใ‚ธใƒงใƒ–ใซใฏใƒ‡ใƒผใ‚ฟใ‚ปใƒƒใƒˆใฎใƒ‘ใ‚ฟใƒผใƒณใŒๅฟ…่ฆใงใ™",
+                "msg/a map/reduce script must define mapper(files, emit) and reducer(key, values)": "map/reduce ใ‚นใ‚ฏใƒชใƒ—ใƒˆใฏ mapper(files, emit) ใจ reducer(key, values) ใ‚’ๅฎš็พฉใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™",
+                "msg/advertise URL must start with http:// or https://": "ๅ…ฌ้–‹ URL ใฏ http:// ใพใŸใฏ https:// ใงๅง‹ใพใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™",
+                "msg/args must be valid JSON": "ๅผ•ๆ•ฐใฏๆœ‰ๅŠนใช JSON ใงใ‚ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™",
+                "msg/cannot remove the namespace root": "ๅๅ‰็ฉบ้–“ใฎใƒซใƒผใƒˆใฏๅ‰Š้™คใงใใพใ›ใ‚“",
+                "msg/checksum mismatch": "ใƒใ‚งใƒƒใ‚ฏใ‚ตใƒ ใŒไธ€่‡ดใ—ใพใ›ใ‚“",
+                "msg/chunk checksum mismatch": "ใƒใƒฃใƒณใ‚ฏใฎใƒใ‚งใƒƒใ‚ฏใ‚ตใƒ ใŒไธ€่‡ดใ—ใพใ›ใ‚“",
+                "msg/chunk exceeds file size": "ใƒใƒฃใƒณใ‚ฏใŒใƒ•ใ‚กใ‚คใƒซใ‚ตใ‚คใ‚บใ‚’่ถ…ใˆใฆใ„ใพใ™",
+                "msg/chunk index out of range": "ใƒใƒฃใƒณใ‚ฏ็•ชๅทใŒ็ฏ„ๅ›ฒๅค–ใงใ™",
+                "msg/chunk size invalid": "ใƒใƒฃใƒณใ‚ฏใ‚ตใ‚คใ‚บใŒ็„กๅŠนใงใ™",
+                "msg/cluster jobs not available": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใ‚ธใƒงใƒ–ใฏๅˆฉ็”จใงใใพใ›ใ‚“",
+                "msg/cluster name cannot be empty": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผๅใ‚’็ฉบใซใฏใงใใพใ›ใ‚“",
+                "msg/cluster name required": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผๅใŒๅฟ…่ฆใงใ™",
+                "msg/directory not empty": "ใƒ•ใ‚ฉใƒซใƒ€ใƒผใŒ็ฉบใงใฏใ‚ใ‚Šใพใ›ใ‚“",
+                "msg/disk usage not supported on this platform": "ใ“ใฎใƒ—ใƒฉใƒƒใƒˆใƒ•ใ‚ฉใƒผใƒ ใงใฏใƒ‡ใ‚ฃใ‚นใ‚ฏไฝฟ็”จ้‡ใ‚’ๅ–ๅพ—ใงใใพใ›ใ‚“",
+                "msg/drive required": "ใƒ‰ใƒฉใ‚คใƒ–ใŒๅฟ…่ฆใงใ™",
+                "msg/empty response": "ๅฟœ็ญ”ใŒ็ฉบใงใ™",
+                "msg/enabled must be true or false": "enabled ใฏ true ใ‹ false ใงใ‚ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™",
+                "msg/event bus not available": "ใ‚คใƒ™ใƒณใƒˆใƒใ‚นใฏๅˆฉ็”จใงใใพใ›ใ‚“",
+                "msg/event data too large (64 KB max)": "ใ‚คใƒ™ใƒณใƒˆใƒ‡ใƒผใ‚ฟใŒๅคงใใ™ใŽใพใ™๏ผˆๆœ€ๅคง 64 KB๏ผ‰",
+                "msg/event type required": "ใ‚คใƒ™ใƒณใƒˆใฎ็จฎ้กžใŒๅฟ…่ฆใงใ™",
+                "msg/evict notice is not for this node": "ๅ‰Š้™ค้€š็Ÿฅใฏใ“ใฎใƒŽใƒผใƒ‰ๅฎ›ใฆใงใฏใ‚ใ‚Šใพใ›ใ‚“",
+                "msg/file changed since the copy was planned": "ใ‚ณใƒ”ใƒผใฎ่จˆ็”ปๅพŒใซใƒ•ใ‚กใ‚คใƒซใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ",
+                "msg/file no longer exists in the namespace": "ใƒ•ใ‚กใ‚คใƒซใฏใ‚‚ใ†ๅๅ‰็ฉบ้–“ใซๅญ˜ๅœจใ—ใพใ›ใ‚“",
+                "msg/file not found on volume": "ใƒœใƒชใƒฅใƒผใƒ ใซใƒ•ใ‚กใ‚คใƒซใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“",
+                "msg/folder path required": "ใƒ•ใ‚ฉใƒซใƒ€ใƒผใฎใƒ‘ใ‚นใŒๅฟ…่ฆใงใ™",
+                "msg/folder required": "ใƒ•ใ‚ฉใƒซใƒ€ใƒผใŒๅฟ…่ฆใงใ™",
+                "msg/hook not found": "ใƒ•ใƒƒใ‚ฏใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“",
+                "msg/insufficient cpu cores": "CPU ใ‚ณใ‚ขใŒไธ่ถณใ—ใฆใ„ใพใ™",
+                "msg/insufficient memory": "ใƒกใƒขใƒชใŒไธ่ถณใ—ใฆใ„ใพใ™",
+                "msg/invalid append payload": "็„กๅŠนใช่ฟฝ่จ˜ใƒ‡ใƒผใ‚ฟใงใ™",
+                "msg/invalid batch": "็„กๅŠนใชใƒใƒƒใƒใงใ™",
+                "msg/invalid begin request": "็„กๅŠนใช้–‹ๅง‹ใƒชใ‚ฏใ‚จใ‚นใƒˆใงใ™",
+                "msg/invalid entry": "็„กๅŠนใชใ‚จใƒณใƒˆใƒชใƒผใงใ™",
+                "msg/invalid heartbeat": "็„กๅŠนใชใƒใƒผใƒˆใƒ“ใƒผใƒˆใงใ™",
+                "msg/invalid job spec": "็„กๅŠนใชใ‚ธใƒงใƒ–ไป•ๆง˜ใงใ™",
+                "msg/invalid join request": "็„กๅŠนใชๅ‚ๅŠ ใƒชใ‚ฏใ‚จใ‚นใƒˆใงใ™",
+                "msg/invalid lease": "็„กๅŠนใชใƒชใƒผใ‚นใงใ™",
+                "msg/invalid node URL": "ใƒŽใƒผใƒ‰ URL ใŒ็„กๅŠนใงใ™",
+                "msg/invalid node public key": "ใƒŽใƒผใƒ‰ใฎๅ…ฌ้–‹้ตใŒ็„กๅŠนใงใ™",
+                "msg/invalid node record": "็„กๅŠนใชใƒŽใƒผใƒ‰ใƒฌใ‚ณใƒผใƒ‰ใงใ™",
+                "msg/invalid range": "็„กๅŠนใช็ฏ„ๅ›ฒใงใ™",
+                "msg/invalid rename": "็„กๅŠนใชๅๅ‰ใฎๅค‰ๆ›ดใงใ™",
+                "msg/invalid report": "็„กๅŠนใชใƒฌใƒใƒผใƒˆใงใ™",
+                "msg/invalid request": "็„กๅŠนใชใƒชใ‚ฏใ‚จใ‚นใƒˆใงใ™",
+                "msg/invalid sync payload": "็„กๅŠนใชๅŒๆœŸใƒ‡ใƒผใ‚ฟใงใ™",
+                "msg/invalid task": "็„กๅŠนใชใ‚ฟใ‚นใ‚ฏใงใ™",
+                "msg/invalid username": "ใƒฆใƒผใ‚ถใƒผๅใŒ็„กๅŠนใงใ™",
+                "msg/is a directory": "ใƒ•ใ‚ฉใƒซใƒ€ใƒผใงใ™",
+                "msg/job cancelled": "ใ‚ธใƒงใƒ–ใฏใ‚ญใƒฃใƒณใ‚ปใƒซใ•ใ‚Œใพใ—ใŸ",
+                "msg/job id required": "ใ‚ธใƒงใƒ– ID ใŒๅฟ…่ฆใงใ™",
+                "msg/job is limited to other nodes": "ใ“ใฎใ‚ธใƒงใƒ–ใฏไป–ใฎใƒŽใƒผใƒ‰ใซ้™ๅฎšใ•ใ‚Œใฆใ„ใพใ™",
+                "msg/job not found": "ใ‚ธใƒงใƒ–ใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“",
+                "msg/job owner is required": "ใ‚ธใƒงใƒ–ใฎๆ‰€ๆœ‰่€…ใŒๅฟ…่ฆใงใ™",
+                "msg/job script is empty": "ใ‚ธใƒงใƒ–ใฎใ‚นใ‚ฏใƒชใƒ—ใƒˆใŒ็ฉบใงใ™",
+                "msg/job timed out": "ใ‚ธใƒงใƒ–ใŒใ‚ฟใ‚คใƒ ใ‚ขใ‚ฆใƒˆใ—ใพใ—ใŸ",
+                "msg/join response does not match the token's cluster": "ๅ‚ๅŠ ใฎๅฟœ็ญ”ใŒใƒˆใƒผใ‚ฏใƒณใฎใ‚ฏใƒฉใ‚นใ‚ฟใƒผใจไธ€่‡ดใ—ใพใ›ใ‚“",
+                "msg/join token has an invalid node URL": "ๅ‚ๅŠ ใƒˆใƒผใ‚ฏใƒณใฎใƒŽใƒผใƒ‰ URL ใŒ็„กๅŠนใงใ™",
+                "msg/join token is corrupted": "ๅ‚ๅŠ ใƒˆใƒผใ‚ฏใƒณใŒๅฃŠใ‚Œใฆใ„ใพใ™",
+                "msg/join token is for another cluster": "ๅ‚ๅŠ ใƒˆใƒผใ‚ฏใƒณใฏๅˆฅใฎใ‚ฏใƒฉใ‚นใ‚ฟใƒผใฎใ‚‚ใฎใงใ™",
+                "msg/join token is incomplete": "ๅ‚ๅŠ ใƒˆใƒผใ‚ฏใƒณใŒไธๅฎŒๅ…จใงใ™",
+                "msg/join token is invalid or expired": "ๅ‚ๅŠ ใƒˆใƒผใ‚ฏใƒณใŒ็„กๅŠนใ‹ๆœŸ้™ๅˆ‡ใ‚Œใงใ™",
+                "msg/join token required": "ๅ‚ๅŠ ใƒˆใƒผใ‚ฏใƒณใŒๅฟ…่ฆใงใ™",
+                "msg/metadata store not available": "ใƒกใ‚ฟใƒ‡ใƒผใ‚ฟใ‚นใƒˆใ‚ขใฏๅˆฉ็”จใงใใพใ›ใ‚“",
+                "msg/no cluster volume available; add one in System Settings > Cluster Settings > Storage": "ไฝฟ็”จใงใใ‚‹ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใƒœใƒชใƒฅใƒผใƒ ใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚ใ€Œใ‚ทใ‚นใƒ†ใƒ ่จญๅฎš > ใ‚ฏใƒฉใ‚นใ‚ฟใƒผ่จญๅฎš > ใ‚นใƒˆใƒฌใƒผใ‚ธใ€ใง่ฟฝๅŠ ใ—ใฆใใ ใ•ใ„",
+                "msg/no healthy copy of this file is reachable right now": "ใ“ใฎใƒ•ใ‚กใ‚คใƒซใฎๆญฃๅธธใชใ‚ณใƒ”ใƒผใซ็พๅœจๅˆฐ้”ใงใใพใ›ใ‚“",
+                "msg/no node is available": "ๅˆฉ็”จใงใใ‚‹ใƒŽใƒผใƒ‰ใŒใ‚ใ‚Šใพใ›ใ‚“",
+                "msg/node already has enough copies in flight": "ใ“ใฎใƒŽใƒผใƒ‰ใงใฏๆ—ขใซๅๅˆ†ใชใ‚ณใƒ”ใƒผใŒ้€ฒ่กŒไธญใงใ™",
+                "msg/node id is required": "ใƒŽใƒผใƒ‰ ID ใŒๅฟ…่ฆใงใ™",
+                "msg/node id required": "ใƒŽใƒผใƒ‰ ID ใŒๅฟ…่ฆใงใ™",
+                "msg/node is DEGRADED": "ใƒŽใƒผใƒ‰ใฎๆ€ง่ƒฝใŒไฝŽไธ‹ใ—ใฆใ„ใพใ™",
+                "msg/node is DRAINING": "ใƒŽใƒผใƒ‰ใฏใƒ‰ใƒฌใ‚คใƒณไธญใงใ™",
+                "msg/node is MAINTENANCE": "ใƒŽใƒผใƒ‰ใฏใƒกใƒณใƒ†ใƒŠใƒณใ‚นไธญใงใ™",
+                "msg/node is OFFLINE": "ใƒŽใƒผใƒ‰ใฏใ‚ชใƒ•ใƒฉใ‚คใƒณใงใ™",
+                "msg/node is UNKNOWN": "ใƒŽใƒผใƒ‰ใฎ็Šถๆ…‹ใŒไธๆ˜Žใงใ™",
+                "msg/node is degraded": "ใƒŽใƒผใƒ‰ใฎๆ€ง่ƒฝใŒไฝŽไธ‹ใ—ใฆใ„ใพใ™",
+                "msg/node is draining": "ใƒŽใƒผใƒ‰ใฏใƒ‰ใƒฌใ‚คใƒณไธญใงใ™",
+                "msg/node is maintenance": "ใƒŽใƒผใƒ‰ใฏใƒกใƒณใƒ†ใƒŠใƒณใ‚นไธญใงใ™",
+                "msg/node is not available": "ใƒŽใƒผใƒ‰ใฏๅˆฉ็”จใงใใพใ›ใ‚“",
+                "msg/node is offline": "ใƒŽใƒผใƒ‰ใฏใ‚ชใƒ•ใƒฉใ‚คใƒณใงใ™",
+                "msg/node is unknown": "ใƒŽใƒผใƒ‰ใฎ็Šถๆ…‹ใŒไธๆ˜Žใงใ™",
+                "msg/node lost while running the job": "ใ‚ธใƒงใƒ–ใฎๅฎŸ่กŒไธญใซใƒŽใƒผใƒ‰ใจใฎๆŽฅ็ถšใŒๅคฑใ‚ใ‚Œใพใ—ใŸ",
+                "msg/node name cannot be empty": "ใƒŽใƒผใƒ‰ๅใ‚’็ฉบใซใฏใงใใพใ›ใ‚“",
+                "msg/node not found": "ใƒŽใƒผใƒ‰ใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“",
+                "msg/node refused this job earlier": "ใ“ใฎใƒŽใƒผใƒ‰ใฏไปฅๅ‰ใ“ใฎใ‚ธใƒงใƒ–ใ‚’ๆ‹’ๅฆใ—ใพใ—ใŸ",
+                "msg/not a directory": "ใƒ•ใ‚ฉใƒซใƒ€ใƒผใงใฏใ‚ใ‚Šใพใ›ใ‚“",
+                "msg/not a file": "ใƒ•ใ‚กใ‚คใƒซใงใฏใ‚ใ‚Šใพใ›ใ‚“",
+                "msg/not an ArozOS join token": "ArozOS ใฎๅ‚ๅŠ ใƒˆใƒผใ‚ฏใƒณใงใฏใ‚ใ‚Šใพใ›ใ‚“",
+                "msg/not in a cluster": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใซๅ‚ๅŠ ใ—ใฆใ„ใพใ›ใ‚“",
+                "msg/not logged in": "ใƒญใ‚ฐใ‚คใƒณใ—ใฆใ„ใพใ›ใ‚“",
+                "msg/offset beyond end of file": "ใ‚ชใƒ•ใ‚ปใƒƒใƒˆใŒใƒ•ใ‚กใ‚คใƒซใฎ็ต‚ใ‚ใ‚Šใ‚’่ถ…ใˆใฆใ„ใพใ™",
+                "msg/only the metadata leader assigns jobs": "ใ‚ธใƒงใƒ–ใ‚’ๅ‰ฒใ‚Šๅฝ“ใฆใ‚‰ใ‚Œใ‚‹ใฎใฏใƒกใ‚ฟใƒ‡ใƒผใ‚ฟใฎใƒชใƒผใƒ€ใƒผใ ใ‘ใงใ™",
+                "msg/only the metadata leader assigns replication tasks": "ใƒฌใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใ‚ฟใ‚นใ‚ฏใ‚’ๅ‰ฒใ‚Šๅฝ“ใฆใ‚‰ใ‚Œใ‚‹ใฎใฏใƒกใ‚ฟใƒ‡ใƒผใ‚ฟใฎใƒชใƒผใƒ€ใƒผใ ใ‘ใงใ™",
+                "msg/owner, script and at least one event type are required": "ๆ‰€ๆœ‰่€…ใ€ใ‚นใ‚ฏใƒชใƒ—ใƒˆใ€ๅฐ‘ใชใใจใ‚‚ 1 ใคใฎใ‚คใƒ™ใƒณใƒˆใฎ็จฎ้กžใŒๅฟ…่ฆใงใ™",
+                "msg/path already exists": "ใƒ‘ใ‚นใฏๆ—ขใซๅญ˜ๅœจใ—ใพใ™",
+                "msg/path escapes the volume": "ใƒ‘ใ‚นใŒใƒœใƒชใƒฅใƒผใƒ ใฎๅค–ใ‚’ๆŒ‡ใ—ใฆใ„ใพใ™",
+                "msg/path not found in the cluster namespace": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใฎๅๅ‰็ฉบ้–“ใซใƒ‘ใ‚นใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“",
+                "msg/path required": "ใƒ‘ใ‚นใŒๅฟ…่ฆใงใ™",
+                "msg/peer has no reachable endpoint": "ใƒ”ใ‚ขใซๅˆฐ้”ๅฏ่ƒฝใชใ‚จใƒณใƒ‰ใƒใ‚คใƒณใƒˆใŒใ‚ใ‚Šใพใ›ใ‚“",
+                "msg/permission denied": "ๆจฉ้™ใŒใ‚ใ‚Šใพใ›ใ‚“",
+                "msg/previous node stopped reporting": "ๅ‰ใฎใƒŽใƒผใƒ‰ใŒๅฟœ็ญ”ใ‚’ๅœๆญขใ—ใพใ—ใŸ",
+                "msg/refusing to delete the volume root": "ใƒœใƒชใƒฅใƒผใƒ ใฎใƒซใƒผใƒˆใฏๅ‰Š้™คใงใใพใ›ใ‚“",
+                "msg/refusing to drop the last healthy copy": "ๆœ€ๅพŒใฎๆญฃๅธธใชใ‚ณใƒ”ใƒผใฏๅ‰Š้™คใงใใพใ›ใ‚“",
+                "msg/replicas must be between 0 and 16": "ใ‚ณใƒ”ใƒผๆ•ฐใฏ 0 ใ‹ใ‚‰ 16 ใฎ้–“ใงใ‚ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™",
+                "msg/replicas must be between 1 and 16": "ใ‚ณใƒ”ใƒผๆ•ฐใฏ 1 ใ‹ใ‚‰ 16 ใฎ้–“ใงใ‚ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™",
+                "msg/request is not signed": "ใƒชใ‚ฏใ‚จใ‚นใƒˆใŒ็ฝฒๅใ•ใ‚Œใฆใ„ใพใ›ใ‚“",
+                "msg/request signature invalid": "ใƒชใ‚ฏใ‚จใ‚นใƒˆใฎ็ฝฒๅใŒ็„กๅŠนใงใ™",
+                "msg/request targets another cluster": "ใƒชใ‚ฏใ‚จใ‚นใƒˆใฎๅฎ›ๅ…ˆใŒๅˆฅใฎใ‚ฏใƒฉใ‚นใ‚ฟใƒผใงใ™",
+                "msg/request timestamp outside the accepted window": "ใƒชใ‚ฏใ‚จใ‚นใƒˆใฎใ‚ฟใ‚คใƒ ใ‚นใ‚ฟใƒณใƒ—ใŒ่จฑๅฎน็ฏ„ๅ›ฒๅค–ใงใ™๏ผˆใƒŽใƒผใƒ‰ใฎๆ™‚่จˆใ‚’็ขบ่ชใ—ใฆใใ ใ•ใ„๏ผ‰",
+                "msg/requested log range was compacted, take a snapshot": "่ฆๆฑ‚ใ•ใ‚ŒใŸใƒญใ‚ฐ็ฏ„ๅ›ฒใฏๅœง็ธฎๆธˆใฟใงใ™ใ€‚ใ‚นใƒŠใƒƒใƒ—ใ‚ทใƒงใƒƒใƒˆใ‚’ๅ–ๅพ—ใ—ใฆใใ ใ•ใ„",
+                "msg/script path required": "ใ‚นใ‚ฏใƒชใƒ—ใƒˆใฎใƒ‘ใ‚นใŒๅฟ…่ฆใงใ™",
+                "msg/sender is not a member of this cluster": "้€ไฟกๅ…ƒใฏใ“ใฎใ‚ฏใƒฉใ‚นใ‚ฟใƒผใฎใƒกใƒณใƒใƒผใงใฏใ‚ใ‚Šใพใ›ใ‚“",
+                "msg/shutting down": "ใ‚ทใƒฃใƒƒใƒˆใƒ€ใ‚ฆใƒณไธญ",
+                "msg/source copy is not readable": "ใ‚ณใƒ”ใƒผๅ…ƒใ‚’่ชญใฟๅ–ใ‚Œใพใ›ใ‚“",
+                "msg/target node is not connected through a tunnel on this node": "ๅฏพ่ฑกใƒŽใƒผใƒ‰ใฏใ“ใฎใƒŽใƒผใƒ‰ใฎใƒˆใƒณใƒใƒซใซๆŽฅ็ถšใ•ใ‚Œใฆใ„ใพใ›ใ‚“",
+                "msg/task is for another node": "ใ“ใฎใ‚ฟใ‚นใ‚ฏใฏๅˆฅใฎใƒŽใƒผใƒ‰ๅฎ›ใฆใงใ™",
+                "msg/the URL answered but it is not an ArozOS cluster endpoint": "URL ใฏๅฟœ็ญ”ใ—ใพใ—ใŸใŒใ€ArozOS ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใฎใ‚จใƒณใƒ‰ใƒใ‚คใƒณใƒˆใงใฏใ‚ใ‚Šใพใ›ใ‚“",
+                "msg/the job has no dataset pattern": "ใ“ใฎใ‚ธใƒงใƒ–ใซใฏใƒ‡ใƒผใ‚ฟใ‚ปใƒƒใƒˆใฎใƒ‘ใ‚ฟใƒผใƒณใŒใ‚ใ‚Šใพใ›ใ‚“",
+                "msg/the job owner has no account on this node": "ใ“ใฎใƒŽใƒผใƒ‰ใซใ‚ธใƒงใƒ–ๆ‰€ๆœ‰่€…ใฎใ‚ขใ‚ซใ‚ฆใƒณใƒˆใŒใ‚ใ‚Šใพใ›ใ‚“",
+                "msg/the mappers emitted more than 8 MB; narrow the dataset or emit fewer values": "mapper ใฎๅ‡บๅŠ›ใŒ 8 MB ใ‚’่ถ…ใˆใพใ—ใŸใ€‚ใƒ‡ใƒผใ‚ฟใ‚ปใƒƒใƒˆใ‚’็ตžใ‚‹ใ‹ใ€ๅ‡บๅŠ›ใ™ใ‚‹ๅ€คใ‚’ๆธ›ใ‚‰ใ—ใฆใใ ใ•ใ„",
+                "msg/the user root itself cannot be a cluster volume; pick a sub folder such as /cluster": "ใƒฆใƒผใ‚ถใƒผใฎใƒซใƒผใƒˆ่‡ชไฝ“ใฏใ‚ฏใƒฉใ‚นใ‚ฟใƒผใƒœใƒชใƒฅใƒผใƒ ใซใงใใพใ›ใ‚“ใ€‚/cluster ใฎใ‚ˆใ†ใชใ‚ตใƒ–ใƒ•ใ‚ฉใƒซใƒ€ใƒผใ‚’้ธใ‚“ใงใใ ใ•ใ„",
+                "msg/this folder is already a cluster volume": "ใ“ใฎใƒ•ใ‚ฉใƒซใƒ€ใƒผใฏๆ—ขใซใ‚ฏใƒฉใ‚นใ‚ฟใƒผใƒœใƒชใƒฅใƒผใƒ ใงใ™",
+                "msg/this job script defines no run(job) function": "ใ“ใฎใ‚ธใƒงใƒ–ใ‚นใ‚ฏใƒชใƒ—ใƒˆใซใฏ run(job) ้–ขๆ•ฐใŒใ‚ใ‚Šใพใ›ใ‚“",
+                "msg/this node cannot execute jobs": "ใ“ใฎใƒŽใƒผใƒ‰ใฏใ‚ธใƒงใƒ–ใ‚’ๅฎŸ่กŒใงใใพใ›ใ‚“",
+                "msg/this node has no advertised URL; generate the join token on a node that other nodes can reach": "ใ“ใฎใƒŽใƒผใƒ‰ใซใฏๅ…ฌ้–‹ URL ใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚ไป–ใฎใƒŽใƒผใƒ‰ใ‹ใ‚‰ๅˆฐ้”ใงใใ‚‹ใƒŽใƒผใƒ‰ใงๅ‚ๅŠ ใƒˆใƒผใ‚ฏใƒณใ‚’็”Ÿๆˆใ—ใฆใใ ใ•ใ„",
+                "msg/this node is already part of a cluster, leave it first": "ใ“ใฎใƒŽใƒผใƒ‰ใฏๆ—ขใซใ‚ฏใƒฉใ‚นใ‚ฟใƒผใซๅ‚ๅŠ ใ—ใฆใ„ใพใ™ใ€‚ๅ…ˆใซ้›ข่„ฑใ—ใฆใใ ใ•ใ„",
+                "msg/this node is not part of a cluster": "ใ“ใฎใƒŽใƒผใƒ‰ใฏใ‚ฏใƒฉใ‚นใ‚ฟใƒผใซๅ‚ๅŠ ใ—ใฆใ„ใพใ›ใ‚“",
+                "msg/this node is not the identity origin of the cluster": "ใ“ใฎใƒŽใƒผใƒ‰ใฏใ‚ฏใƒฉใ‚นใ‚ฟใƒผใฎ ID ใ‚ชใƒชใ‚ธใƒณใงใฏใ‚ใ‚Šใพใ›ใ‚“",
+                "msg/this node is not the metadata leader": "ใ“ใฎใƒŽใƒผใƒ‰ใฏใƒกใ‚ฟใƒ‡ใƒผใ‚ฟใฎใƒชใƒผใƒ€ใƒผใงใฏใ‚ใ‚Šใพใ›ใ‚“",
+                "msg/timed out waiting for the job to finish": "ใ‚ธใƒงใƒ–ใฎๅฎŒไบ†ๅพ…ใกใŒใ‚ฟใ‚คใƒ ใ‚ขใ‚ฆใƒˆใ—ใพใ—ใŸ",
+                "msg/token id required": "ใƒˆใƒผใ‚ฏใƒณ ID ใŒๅฟ…่ฆใงใ™",
+                "msg/token not found": "ใƒˆใƒผใ‚ฏใƒณใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“",
+                "msg/tunnel closed": "ใƒˆใƒณใƒใƒซใŒ้–‰ใ˜ใ‚‰ใ‚Œใพใ—ใŸ",
+                "msg/unknown upload session": "ไธๆ˜Žใชใ‚ขใƒƒใƒ—ใƒญใƒผใƒ‰ใ‚ปใƒƒใ‚ทใƒงใƒณใงใ™",
+                "msg/unreachable": "ๅˆฐ้”ไธ่ƒฝ",
+                "msg/unsupported node state": "ใ‚ตใƒใƒผใƒˆใ•ใ‚Œใฆใ„ใชใ„ใƒŽใƒผใƒ‰็Šถๆ…‹ใงใ™",
+                "msg/upload incomplete": "ใ‚ขใƒƒใƒ—ใƒญใƒผใƒ‰ใŒๅฎŒไบ†ใ—ใฆใ„ใพใ›ใ‚“",
+                "msg/url required": "URL ใŒๅฟ…่ฆใงใ™",
+                "msg/use leave to remove this node": "ใ“ใฎใƒŽใƒผใƒ‰ใ‚’ๅ‰Š้™คใ™ใ‚‹ใซใฏใ€Œใ‚ฏใƒฉใ‚นใ‚ฟใƒผใ‹ใ‚‰้›ข่„ฑใ€ใ‚’ไฝฟใฃใฆใใ ใ•ใ„",
+                "msg/user assertion expired": "ใƒฆใƒผใ‚ถใƒผใ‚ขใ‚ตใƒผใ‚ทใƒงใƒณใฎๆœŸ้™ใŒๅˆ‡ใ‚Œใพใ—ใŸ",
+                "msg/user assertion invalid": "ใƒฆใƒผใ‚ถใƒผใ‚ขใ‚ตใƒผใ‚ทใƒงใƒณใŒ็„กๅŠนใงใ™",
+                "msg/user not found on the identity origin": "ID ใ‚ชใƒชใ‚ธใƒณใซใƒฆใƒผใ‚ถใƒผใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“",
+                "msg/volume id required": "ใƒœใƒชใƒฅใƒผใƒ  ID ใŒๅฟ…่ฆใงใ™",
+                "msg/volume is not on this node": "ใƒœใƒชใƒฅใƒผใƒ ใฏใ“ใฎใƒŽใƒผใƒ‰ใซใ‚ใ‚Šใพใ›ใ‚“",
+                "msg/volume not found": "ใƒœใƒชใƒฅใƒผใƒ ใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“",
+                "msg/volume not found on this node": "ใ“ใฎใƒŽใƒผใƒ‰ใซใƒœใƒชใƒฅใƒผใƒ ใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“",
+                "msgp/architecture {0} not allowed": "ใ‚ขใƒผใ‚ญใƒ†ใ‚ฏใƒใƒฃ {0} ใฏ่จฑๅฏใ•ใ‚Œใฆใ„ใพใ›ใ‚“",
+                "msgp/cancelled by {0}": "{0} ใŒใ‚ญใƒฃใƒณใ‚ปใƒซใ—ใพใ—ใŸ",
+                "msgp/could not assemble the reduce input: {0}": "reduce ใฎๅ…ฅๅŠ›ใ‚’็ต„ใฟ็ซ‹ใฆใ‚‰ใ‚Œใพใ›ใ‚“ใงใ—ใŸ: {0}",
+                "msgp/could not create a map task: {0}": "map ใ‚ฟใ‚นใ‚ฏใ‚’ไฝœๆˆใงใใพใ›ใ‚“ใงใ—ใŸ: {0}",
+                "msgp/could not create the reduce task: {0}": "reduce ใ‚ฟใ‚นใ‚ฏใ‚’ไฝœๆˆใงใใพใ›ใ‚“ใงใ—ใŸ: {0}",
+                "msgp/drive {0} is not a local drive on this node": "ใƒ‰ใƒฉใ‚คใƒ– {0} ใฏใ“ใฎใƒŽใƒผใƒ‰ใฎใƒญใƒผใ‚ซใƒซใƒ‰ใƒฉใ‚คใƒ–ใงใฏใ‚ใ‚Šใพใ›ใ‚“",
+                "msgp/hook script not found: {0}": "ใƒ•ใƒƒใ‚ฏใ‚นใ‚ฏใƒชใƒ—ใƒˆใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“: {0}",
+                "msgp/invalid join response from {0}": "{0} ใ‹ใ‚‰ใฎๅ‚ๅŠ ๅฟœ็ญ”ใŒ็„กๅŠนใงใ™",
+                "msgp/invalid node key encoding: {0}": "ใƒŽใƒผใƒ‰ใ‚ญใƒผใฎใ‚จใƒณใ‚ณใƒผใƒ‰ใŒ็„กๅŠนใงใ™: {0}",
+                "msgp/invalid public key encoding: {0}": "ๅ…ฌ้–‹้ตใฎใ‚จใƒณใ‚ณใƒผใƒ‰ใŒ็„กๅŠนใงใ™: {0}",
+                "msgp/job timed out after {0}s": "ใ‚ธใƒงใƒ–ใฏ {0} ็ง’ๅพŒใซใ‚ฟใ‚คใƒ ใ‚ขใ‚ฆใƒˆใ—ใพใ—ใŸ",
+                "msgp/local drive {0} is not mounted": "ใƒญใƒผใ‚ซใƒซใƒ‰ใƒฉใ‚คใƒ– {0} ใฏใƒžใ‚ฆใƒณใƒˆใ•ใ‚Œใฆใ„ใพใ›ใ‚“",
+                "msgp/missing feature {0}": "ๆฉŸ่ƒฝ {0} ใŒใ‚ใ‚Šใพใ›ใ‚“",
+                "msgp/no cluster volume available; add one in System Settings > Cluster Settings > Storage ({0})": "ไฝฟ็”จใงใใ‚‹ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใƒœใƒชใƒฅใƒผใƒ ใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚ใ€Œใ‚ทใ‚นใƒ†ใƒ ่จญๅฎš > ใ‚ฏใƒฉใ‚นใ‚ฟใƒผ่จญๅฎš > ใ‚นใƒˆใƒฌใƒผใ‚ธใ€ใง่ฟฝๅŠ ใ—ใฆใใ ใ•ใ„๏ผˆ{0}๏ผ‰",
+                "msgp/no reachable copy of: {0}": "ๆฌกใฎใƒ•ใ‚กใ‚คใƒซใฎใ‚ณใƒ”ใƒผใซๅˆฐ้”ใงใใพใ›ใ‚“: {0}",
+                "msgp/node {0} could not take the job: {1}": "ใƒŽใƒผใƒ‰ {0} ใฏใ‚ธใƒงใƒ–ใ‚’ๅ—ใ‘ๅ–ใ‚Œใพใ›ใ‚“ใงใ—ใŸ: {1}",
+                "msgp/os {0} not allowed": "OS {0} ใฏ่จฑๅฏใ•ใ‚Œใฆใ„ใพใ›ใ‚“",
+                "msgp/permission denied: {0}": "ๆจฉ้™ใŒใ‚ใ‚Šใพใ›ใ‚“: {0}",
+                "msgp/remote node returned {0}: {1}": "ใƒชใƒขใƒผใƒˆใƒŽใƒผใƒ‰ใŒ {0} ใ‚’่ฟ”ใ—ใพใ—ใŸ: {1}",
+                "msgp/script not found: {0}": "ใ‚นใ‚ฏใƒชใƒ—ใƒˆใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“: {0}",
+                "msgp/this volume holds the only copy of {0} file(s); evacuate it first": "ใ“ใฎใƒœใƒชใƒฅใƒผใƒ ใซใฏ {0} ๅ€‹ใฎใƒ•ใ‚กใ‚คใƒซใฎๅ”ฏไธ€ใฎใ‚ณใƒ”ใƒผใŒใ‚ใ‚Šใพใ™ใ€‚ๅ…ˆใซ้€€้ฟใ—ใฆใใ ใ•ใ„",
+                "msgp/unable to reach {0}: {1}": "{0} ใซๅˆฐ้”ใงใใพใ›ใ‚“: {1}",
+                "msgp/unreachable: {0}": "ๅˆฐ้”ไธ่ƒฝ: {0}",
+                "msgp/unsupported node URL scheme: {0}": "ใ‚ตใƒใƒผใƒˆใ•ใ‚Œใฆใ„ใชใ„ใƒŽใƒผใƒ‰ URL ใ‚นใ‚ญใƒผใƒ ใงใ™: {0}",
+                "msgp/weight {0} must be a number": "้‡ใฟ {0} ใฏๆ•ฐๅ€คใงใ‚ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™",
+                "msgp/weight {0} must be between 0 and 1": "้‡ใฟ {0} ใฏ 0 ใ‹ใ‚‰ 1 ใฎ้–“ใงใ‚ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™",
+                "msgp/{0} produced an unreadable result": "{0} ใŒ่ชญใฟๅ–ใ‚Œใชใ„็ตๆžœใ‚’่ฟ”ใ—ใพใ—ใŸ"
+            },
+            "placeholder": {
+                "Name (optional)": "ๅๅ‰๏ผˆไปปๆ„๏ผ‰",
+                "job id": "ใ‚ธใƒงใƒ– ID",
+                "Nightly index": "ๅคœ้–“ใ‚คใƒณใƒ‡ใƒƒใ‚ฏใ‚น",
+                "50 per task": "ใ‚ฟใ‚นใ‚ฏใ”ใจใซ 50"
+            },
+            "titles": {
+                "Re-detect capabilities": "ๆฉŸ่ƒฝใ‚’ๅ†ๆคœๅ‡บ"
+            }
+        },
+        "ko-kr": {
+            "name": "ํ•œ๊ตญ์–ด",
+            "fontFamily": "\"Microsoft JhengHei\",\"SimHei\", \"Apple LiGothic Medium\", \"STHeiti\"",
+            "strings": {
+                "cluster/ago/d": "{0}์ผ ์ „",
+                "cluster/ago/h": "{0}์‹œ๊ฐ„ ์ „",
+                "cluster/ago/m": "{0}๋ถ„ ์ „",
+                "cluster/ago/s": "{0}์ดˆ ์ „",
+                "cluster/auto": "์ž๋™(์—ฐ๊ฒฐ ๊ฐ€๋Šฅํ•œ ์•„๋ฌด ๊ตฌ์„ฑ์›)",
+                "cluster/cancel": "์ทจ์†Œ",
+                "cluster/caps": "๊ธฐ๋Šฅ",
+                "cluster/cfg/insecure": "๋‹ค๋ฅธ ๋…ธ๋“œ์˜ ์ž์ฒด ์„œ๋ช… ์ธ์ฆ์„œ ํ—ˆ์šฉ(LAN ์ „์šฉ)",
+                "cluster/cfg/name": "๋…ธ๋“œ ์ด๋ฆ„",
+                "cluster/cfg/test": "ํ…Œ์ŠคํŠธ",
+                "cluster/cfg/tunnel": "ํ„ฐ๋„ ๊ฒฝ์œ ",
+                "cluster/cfg/url": "๊ณต๊ฐœ URL(๋‹ค๋ฅธ ๋…ธ๋“œ๊ฐ€ ์ด ๋…ธ๋“œ์— ์—ฐ๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•)",
+                "cluster/cfg/urlhint": "์ด ๋…ธ๋“œ์— ๊ณต๊ฐœ ์ฃผ์†Œ๊ฐ€ ์—†์œผ๋ฉด ๋น„์›Œ ๋‘์„ธ์š”. ๋Œ€์‹  ์—ฐ๊ฒฐ ๊ฐ€๋Šฅํ•œ ๊ตฌ์„ฑ์›๊ณผ ํ„ฐ๋„์„ ์œ ์ง€ํ•˜๋ฉฐ, Cloudflare๋ฅผ ํ†ตํ•ด์„œ๋„ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.",
+                "cluster/clusterid": "ํด๋Ÿฌ์Šคํ„ฐ ID",
+                "cluster/confirmleave": "ํด๋Ÿฌ์Šคํ„ฐ์—์„œ ๋‚˜๊ฐ€์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? ๋‹ค๋ฅธ ๋…ธ๋“œ์˜ ํŒŒ์ผ๊ณผ ์ž‘์—…์— ๋” ์ด์ƒ ์—ฌ๊ธฐ์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.",
+                "cluster/confirmremove": "๋…ธ๋“œ \"{0}\"์„(๋ฅผ) ์ œ๊ฑฐํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?",
+                "cluster/copied": "ํ† ํฐ์„ ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌํ–ˆ์Šต๋‹ˆ๋‹ค",
+                "cluster/copy": "๋ณต์‚ฌ",
+                "cluster/coresvalue": "{0}์ฝ”์–ด",
+                "cluster/create": "์ƒˆ ํด๋Ÿฌ์Šคํ„ฐ ๋งŒ๋“ค๊ธฐ",
+                "cluster/createbtn": "๋งŒ๋“ค๊ธฐ",
+                "cluster/created": "์ƒ์„ฑ ์‹œ๊ฐ„",
+                "cluster/createhint": "์ด ๋…ธ๋“œ๊ฐ€ ์ฒซ ๋ฒˆ์งธ ๊ตฌ์„ฑ์›์ด ๋ฉ๋‹ˆ๋‹ค. ์ดํ›„ ์—ฌ๊ธฐ์—์„œ ์ฐธ๊ฐ€ ํ† ํฐ์„ ์ƒ์„ฑํ•ด ๋…ธ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”.",
+                "cluster/direct": "์ง์ ‘",
+                "cluster/disk": "๋””์Šคํฌ",
+                "cluster/diskfree": "์—ฌ์œ  {0} / ์ „์ฒด {1}",
+                "cluster/drain": "๋“œ๋ ˆ์ธ",
+                "cluster/entername": "ํด๋Ÿฌ์Šคํ„ฐ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”",
+                "cluster/entertoken": "์ฐธ๊ฐ€ ํ† ํฐ์„ ๋ถ™์—ฌ ๋„ฃ์œผ์„ธ์š”",
+                "cluster/enterurl": "ํ…Œ์ŠคํŠธํ•  URL์„ ์ž…๋ ฅํ•˜์„ธ์š”",
+                "cluster/expiresat": "{0}์— ๋งŒ๋ฃŒ",
+                "cluster/folder": "ํด๋”",
+                "cluster/freeamount": "์—ฌ์œ  {0}",
+                "cluster/gentoken": "์ฐธ๊ฐ€ ํ† ํฐ ์ƒ์„ฑ",
+                "cluster/id/accounts": "๊ณ„์ •",
+                "cluster/id/apply": "์ ์šฉ",
+                "cluster/id/conflicts": "๋กœ์ปฌ ์ „์šฉ",
+                "cluster/id/conflictswhy": "๊ฐ™์€ ์ด๋ฆ„์˜ ๋กœ์ปฌ ๊ณ„์ •์€ ๊ทธ๋Œ€๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค",
+                "cluster/id/disabled": "์‚ฌ์šฉ ์•ˆ ํ•จ(๊ฐ ๋…ธ๋“œ๊ฐ€ ์ž์ฒด ๊ณ„์ •์œผ๋กœ ํ™•์ธ)",
+                "cluster/id/fallback": "ID ์›๋ณธ์— ์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†์–ด ๋กœ์ปฌ ๊ณ„์ •์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค",
+                "cluster/id/forward": "์ „๋‹ฌ",
+                "cluster/id/hint": "ID ์›๋ณธ์€ ํด๋Ÿฌ์Šคํ„ฐ์˜ ๋ชจ๋“  ๋กœ๊ทธ์ธ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๊ณ„์ •์€ ๋‹ค๋ฅธ ๋…ธ๋“œ๋กœ ๋ณต์‚ฌ๋˜๋ฏ€๋กœ ์›๋ณธ์— ์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†์„ ๋•Œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฃน์€ ์ด๋ฆ„์œผ๋กœ ๋Œ€์‘ํ•˜๋ฏ€๋กœ ๋ชจ๋“  ๋…ธ๋“œ์— ๊ฐ™์€ ๊ถŒํ•œ ๊ทธ๋ฃน์„ ๋งŒ๋“œ์„ธ์š”.",
+                "cluster/id/lastlogin": "๋งˆ์ง€๋ง‰ ๋กœ๊ทธ์ธ ํ™•์ธ",
+                "cluster/id/lastsync": "๋งˆ์ง€๋ง‰ ๋™๊ธฐํ™”",
+                "cluster/id/local": "๊ฐ ๋…ธ๋“œ๊ฐ€ ์ž์ฒด ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค",
+                "cluster/id/mirrors": "๋กœ์ปฌ ๊ณ„์ • {1}๊ฐœ ์ค‘ {0}๊ฐœ๊ฐ€ ์›๋ณธ๊ณผ ๋™๊ธฐํ™”๋จ",
+                "cluster/id/mode": "๋ชจ๋“œ",
+                "cluster/id/origin": "์›๋ณธ",
+                "cluster/id/originhere": "์ด ๋…ธ๋“œ๊ฐ€ ํด๋Ÿฌ์Šคํ„ฐ ์ „์ฒด์˜ ๋กœ๊ทธ์ธ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค",
+                "cluster/id/published": "๊ตฌ์„ฑ์›์—๊ฒŒ {0}๊ฐœ ๊ฒŒ์‹œ๋จ",
+                "cluster/id/replicated": "๋ณต์ œ๋จ",
+                "cluster/id/skipped": "๊ฑด๋„ˆ๋œ€",
+                "cluster/id/skippedwhy": "์ด ๋…ธ๋“œ์— ํ•ด๋‹น ๊ทธ๋ฃน์ด ํ•˜๋‚˜๋„ ์—†์Šต๋‹ˆ๋‹ค",
+                "cluster/id/sync": "์ง€๊ธˆ ๋™๊ธฐํ™”",
+                "cluster/id/synced": "๊ณ„์ •์„ ๋™๊ธฐํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค",
+                "cluster/id/title": "ID(๋‹จ์ผ ๋กœ๊ทธ์ธ)",
+                "cluster/id/verifiedby": "{0}์—์„œ ๋กœ๊ทธ์ธ ํ™•์ธ",
+                "cluster/info/novolume": "ํด๋Ÿฌ์Šคํ„ฐ์— ์•„์ง ์ €์žฅ์†Œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.",
+                "cluster/info/novolumehint": "ํด๋Ÿฌ์Šคํ„ฐ ์„ค์ •์—์„œ ๋ณผ๋ฅจ์„ ์ถ”๊ฐ€ํ•˜๋ฉด ํŒŒ์ผ ๊ด€๋ฆฌ์ž์— cluster:/ ๋“œ๋ผ์ด๋ธŒ๊ฐ€ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.",
+                "cluster/info/standalone": "์ด ๋…ธ๋“œ๋Š” ํด๋Ÿฌ์Šคํ„ฐ์— ์†ํ•ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.",
+                "cluster/info/standalonehint": "ํด๋Ÿฌ์Šคํ„ฐ ์„ค์ •์—์„œ ํด๋Ÿฌ์Šคํ„ฐ๋ฅผ ๋งŒ๋“ค๊ฑฐ๋‚˜ ์ฐธ๊ฐ€ํ•˜์„ธ์š”. ๊ทธ ์ „๊นŒ์ง€ cluster:/ ๋“œ๋ผ์ด๋ธŒ๋Š” ์ˆจ๊ฒจ์ ธ ์žˆ์Šต๋‹ˆ๋‹ค.",
+                "cluster/info/tagline": "์ด ๋…ธ๋“œ, ๋‹ค๋ฅธ ๋…ธ๋“œ, ๋„ค์ž„์ŠคํŽ˜์ด์Šค์™€ ์Šค์ผ€์ค„๋Ÿฌ์˜ ์ƒํƒœ",
+                "cluster/info/title": "ํด๋Ÿฌ์Šคํ„ฐ ์ •๋ณด",
+                "cluster/invite": "๋…ธ๋“œ ์ถ”๊ฐ€",
+                "cluster/jobs/args": "์ธ์ˆ˜(JSON, ์„ ํƒ)",
+                "cluster/jobs/attemptn": "{0}๋ฒˆ์งธ ์‹œ๋„",
+                "cluster/jobs/browse": "์ฐพ์•„๋ณด๊ธฐ",
+                "cluster/jobs/cancel": "์ทจ์†Œ",
+                "cluster/jobs/complete": "{0} / {1} ์™„๋ฃŒ",
+                "cluster/jobs/dataset": "๋ฐ์ดํ„ฐ ์„ธํŠธ",
+                "cluster/jobs/datasethint": "๋ฐ์ดํ„ฐ ์„ธํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์Šคํฌ๋ฆฝํŠธ์— mapper(files, emit)์™€ reducer(key, values)๋ฅผ ์ •์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํŒŒ์ผ์€ ํ•ด๋‹น ํŒŒ์ผ์„ ๊ฐ€์ง„ ๋…ธ๋“œ์—์„œ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.",
+                "cluster/jobs/datasetlabel": "Map/reduce ๋ฐ์ดํ„ฐ ์„ธํŠธ(์„ ํƒ์  glob, ์ง€์ •ํ•˜๋ฉด map/reduce ์ž‘์—…์ด ๋จ)",
+                "cluster/jobs/detail": "์ž‘์—… ์ƒ์„ธ",
+                "cluster/jobs/duration": "์†Œ์š” ์‹œ๊ฐ„",
+                "cluster/jobs/failedn": "{0}๊ฐœ ์‹คํŒจ",
+                "cluster/jobs/features": "ํ•„์š”ํ•œ ๊ธฐ๋Šฅ",
+                "cluster/jobs/inputs": "์ž…๋ ฅ ๊ฒฝ๋กœ(์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„, ๋ฐ์ดํ„ฐ ๊ฐ€๊นŒ์ด์—์„œ ์‹คํ–‰ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ)",
+                "cluster/jobs/job": "์ž‘์—…",
+                "cluster/jobs/jobs": "์ž‘์—…",
+                "cluster/jobs/kind/map": "map ํ•˜์œ„ ์ž‘์—…",
+                "cluster/jobs/kind/mapreduce": "map/reduce",
+                "cluster/jobs/kind/reduce": "reduce ํ•˜์œ„ ์ž‘์—…",
+                "cluster/jobs/log": "๋กœ๊ทธ",
+                "cluster/jobs/minsecs": "{0}๋ถ„ {1}์ดˆ",
+                "cluster/jobs/name": "์ด๋ฆ„",
+                "cluster/jobs/needscript": "์Šคํฌ๋ฆฝํŠธ ๊ฒฝ๋กœ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "cluster/jobs/node": "๋…ธ๋“œ",
+                "cluster/jobs/nodetail": "์ž‘์—…์„ ์„ ํƒํ•˜๋ฉด ๋กœ๊ทธ์™€ ์ถœ๋ ฅ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค",
+                "cluster/jobs/none": "์•„์ง ์ž‘์—…์ด ์—†์Šต๋‹ˆ๋‹ค",
+                "cluster/jobs/output": "์ถœ๋ ฅ",
+                "cluster/jobs/owner": "์†Œ์œ ์ž",
+                "cluster/jobs/progress": "์ง„ํ–‰๋ฅ ",
+                "cluster/jobs/queued": "์ž‘์—…์ด ๋Œ€๊ธฐ์—ด์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค",
+                "cluster/jobs/run": "์ž‘์—… ์‹คํ–‰",
+                "cluster/jobs/scheduler": "์Šค์ผ€์ค„๋Ÿฌ: {0}",
+                "cluster/jobs/script": "์Šคํฌ๋ฆฝํŠธ",
+                "cluster/jobs/scriptlabel": "์Šคํฌ๋ฆฝํŠธ(run(job)์„ ์ •์˜ํ•˜๋Š” .agi ํŒŒ์ผ)",
+                "cluster/jobs/secs": "{0}์ดˆ",
+                "cluster/jobs/slots": "์ด ๋…ธ๋“œ์—์„œ {0}/{1} ์Šฌ๋กฏ ์‚ฌ์šฉ ์ค‘",
+                "cluster/jobs/status": "์ƒํƒœ",
+                "cluster/jobs/submit": "์ž‘์—… ์ œ์ถœ",
+                "cluster/jobs/submithint": "์Šคํฌ๋ฆฝํŠธ๋Š” ์„ ํƒ๋œ ๋…ธ๋“œ์—์„œ ์‚ฌ์šฉ์ž ๊ถŒํ•œ์œผ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. cluster:/ ๊ฒฝ๋กœ๋Š” ํ‰์†Œ์ฒ˜๋Ÿผ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.",
+                "cluster/jobs/summary": "์‹คํ–‰ ์ค‘ {0}, ๋Œ€๊ธฐ ์ค‘ {1}, ์™„๋ฃŒ {2}",
+                "cluster/jobs/tagline": "๊ฐ€์žฅ ์ ํ•ฉํ•œ ๋…ธ๋“œ์—์„œ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰",
+                "cluster/jobs/tasks": "ํ•˜์œ„ ์ž‘์—…",
+                "cluster/jobs/timeout": "์ œํ•œ ์‹œ๊ฐ„(์ดˆ)",
+                "cluster/jobs/title": "ํด๋Ÿฌ์Šคํ„ฐ ์ž‘์—…",
+                "cluster/jobs/typepath": "์Šคํฌ๋ฆฝํŠธ ๊ฒฝ๋กœ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”(์˜ˆ: user:/jobs/index.agi)",
+                "cluster/jobs/waiting": "๋Œ€๊ธฐ ์ด์œ ",
+                "cluster/join": "๊ธฐ์กด ํด๋Ÿฌ์Šคํ„ฐ์— ์ฐธ๊ฐ€",
+                "cluster/joinbtn": "์ฐธ๊ฐ€",
+                "cluster/joinhint": "ํด๋Ÿฌ์Šคํ„ฐ์˜ ์—ฐ๊ฒฐ ๊ฐ€๋Šฅํ•œ ๊ตฌ์„ฑ์›์—์„œ ์ƒ์„ฑํ•œ ์ฐธ๊ฐ€ ํ† ํฐ์„ ๋ถ™์—ฌ ๋„ฃ์œผ์„ธ์š”.",
+                "cluster/joining": "์ฐธ๊ฐ€ํ•˜๋Š” ์ค‘...",
+                "cluster/justnow": "๋ฐฉ๊ธˆ",
+                "cluster/key": "๋…ธ๋“œ ํ‚ค",
+                "cluster/lastseen": "๋งˆ์ง€๋ง‰ ํ™•์ธ",
+                "cluster/leave": "ํด๋Ÿฌ์Šคํ„ฐ ๋‚˜๊ฐ€๊ธฐ",
+                "cluster/link/info": "ํด๋Ÿฌ์Šคํ„ฐ ์ •๋ณด",
+                "cluster/link/jobs": "ํด๋Ÿฌ์Šคํ„ฐ ์ž‘์—…",
+                "cluster/link/settings": "ํด๋Ÿฌ์Šคํ„ฐ ์„ค์ •",
+                "cluster/load": "๋ถ€ํ•˜",
+                "cluster/maintenance": "์œ ์ง€ ๋ณด์ˆ˜",
+                "cluster/members": "๊ตฌ์„ฑ์›",
+                "cluster/membershint": "์œ ์ง€ ๋ณด์ˆ˜๋Š” ๋…ธ๋“œ๋ฅผ ์Šค์ผ€์ค„๋ง์—์„œ ์ œ์™ธํ•ฉ๋‹ˆ๋‹ค. ๋“œ๋ ˆ์ธ์€ ์‚ฌ๋ณธ๋„ ๋‹ค๋ฅธ ๋…ธ๋“œ๋กœ ์˜ฎ๊ธฐ๋ฏ€๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ๋Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
+                "cluster/meta/entries": "ํ•ญ๋ชฉ",
+                "cluster/meta/entriesvalue": "ํŒŒ์ผ {0}๊ฐœ, ํด๋” {1}๊ฐœ, ๋ณผ๋ฅจ {2}๊ฐœ",
+                "cluster/meta/leader": "๋ฆฌ๋”",
+                "cluster/meta/log": "๋กœ๊ทธ",
+                "cluster/meta/noleader": "์„ ์ถœ ์ค‘",
+                "cluster/meta/pending": "{0}๊ฑด ๋Œ€๊ธฐ ์ค‘",
+                "cluster/meta/term": "์ž„๊ธฐ {0}",
+                "cluster/natonly": "URL ์—†์Œ",
+                "cluster/never": "์—†์Œ",
+                "cluster/node": "๋…ธ๋“œ",
+                "cluster/nodes": "๋…ธ๋“œ",
+                "cluster/nonodes": "๋…ธ๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค",
+                "cluster/noreport": "์•„์ง ๋ณด๊ณ ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค",
+                "cluster/noroute": "๊ฒฝ๋กœ ์—†์Œ",
+                "cluster/notmember": "ํด๋Ÿฌ์Šคํ„ฐ์— ์†ํ•ด ์žˆ์ง€ ์•Š์Œ",
+                "cluster/notunnel": "ํ„ฐ๋„ ์—†์Œ",
+                "cluster/now": "์ง€๊ธˆ",
+                "cluster/platform": "ํ”Œ๋žซํผ",
+                "cluster/platformline": "{0} / {1}, {2}์ฝ”์–ด",
+                "cluster/pol/1": "์‚ฌ๋ณธ 1๊ฐœ",
+                "cluster/pol/2": "์‚ฌ๋ณธ 2๊ฐœ",
+                "cluster/pol/3": "์‚ฌ๋ณธ 3๊ฐœ",
+                "cluster/pol/4": "์‚ฌ๋ณธ 4๊ฐœ",
+                "cluster/pol/copies": "์‚ฌ๋ณธ {0}๊ฐœ",
+                "cluster/pol/hint": "์ •์ฑ…์ด ์—†๋Š” ํด๋”๋Š” ์‚ฌ๋ณธ์„ 1๊ฐœ๋งŒ ๋ณด๊ด€ํ•ฉ๋‹ˆ๋‹ค. ์ถ”๊ฐ€ ์‚ฌ๋ณธ์€ ๋ณต์ œ ์„œ๋น„์Šค๊ฐ€ ๋งŒ๋“ญ๋‹ˆ๋‹ค.",
+                "cluster/pol/label": "์ตœ์ƒ์œ„ ํด๋”๋ณ„ ์‚ฌ๋ณธ ์ˆ˜",
+                "cluster/pol/set": "์„ค์ •",
+                "cluster/pol/title": "์‚ฌ๋ณธ ์ •์ฑ…",
+                "cluster/probe": "์—ฐ๊ฒฐ ํ™•์ธ",
+                "cluster/probefail": "{0}: {1} ({2})",
+                "cluster/probeok": "{0}: {1}ms ({2})",
+                "cluster/reach": "์—ฐ๊ฒฐ ์ƒํƒœ",
+                "cluster/reachableok": "์—ฐ๊ฒฐ ๊ฐ€๋Šฅ, ์™•๋ณต {0}ms",
+                "cluster/redetect": "๋‹ค์‹œ ๊ฐ์ง€",
+                "cluster/remove": "์ œ๊ฑฐ",
+                "cluster/repl/evacuating": "๋ณผ๋ฅจ {0}๊ฐœ ๋น„์šฐ๋Š” ์ค‘",
+                "cluster/repl/files": "ํŒŒ์ผ",
+                "cluster/repl/fromto": "{0} โ†’ {1}",
+                "cluster/repl/healthy": "๋ชจ๋“  ํŒŒ์ผ์ด ์ •์ฑ…์„ ์ถฉ์กฑํ•ฉ๋‹ˆ๋‹ค",
+                "cluster/repl/inflight": "์ง„ํ–‰ ์ค‘",
+                "cluster/repl/inflightvalue": "์‹คํ–‰ ์ค‘ {0}, ๋Œ€๊ธฐ ์ค‘ {1}",
+                "cluster/repl/inspect": "ํŒŒ์ผ ๊ฒ€์‚ฌ",
+                "cluster/repl/inspectbtn": "์‚ฌ๋ณธ ๋ณด๊ธฐ",
+                "cluster/repl/issues": "๋ฌธ์ œ {0}๊ฑด",
+                "cluster/repl/lastrun": "๋งˆ์ง€๋ง‰ ์‹คํ–‰ {0}",
+                "cluster/repl/nocopy": "์ •์ƒ ์‚ฌ๋ณธ ์—†์Œ",
+                "cluster/repl/nolocations": "๊ธฐ๋ก๋œ ์‚ฌ๋ณธ์ด ์—†์Šต๋‹ˆ๋‹ค",
+                "cluster/repl/notasks": "๋ณต์ œ ์ž‘์—…์ด ์—†์Šต๋‹ˆ๋‹ค",
+                "cluster/repl/notfound": "๋„ค์ž„์ŠคํŽ˜์ด์Šค์—์„œ ๊ฒฝ๋กœ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "cluster/repl/over": "์ดˆ๊ณผ ์‚ฌ๋ณธ",
+                "cluster/repl/plan": "์ง€๊ธˆ ๋ณต๊ตฌ",
+                "cluster/repl/planned": "ํ”Œ๋ž˜๋„ˆ ์‹คํ–‰: ๋Œ€๊ธฐ์—ด {0}, ์‚ญ์ œ {1}, ์˜ค๋ž˜๋จ ํ‘œ์‹œ {2}",
+                "cluster/repl/planner": "ํ”Œ๋ž˜๋„ˆ",
+                "cluster/repl/primary": "๊ธฐ๋ณธ",
+                "cluster/repl/reason/evacuate": "๋น„์šฐ๊ธฐ",
+                "cluster/repl/reason/repair": "๋ณต๊ตฌ",
+                "cluster/repl/reason/replica": "์ƒˆ ์‚ฌ๋ณธ",
+                "cluster/repl/route": "๋ณด๋‚ธ ๊ณณ / ๋ฐ›๋Š” ๊ณณ",
+                "cluster/repl/stale": "์˜ค๋ž˜๋œ ์‚ฌ๋ณธ",
+                "cluster/repl/task": "์ž‘์—…",
+                "cluster/repl/title": "๋ณต์ œ",
+                "cluster/repl/under": "์‚ฌ๋ณธ ๋ถ€์กฑ",
+                "cluster/repl/verified": "์‚ฌ๋ณธ {0}๊ฐœ({1}) ๊ฒ€์ฆ, ์˜ค๋ž˜๋จ {2}๊ฐœ",
+                "cluster/repl/verify": "์ด ๋…ธ๋“œ ๊ฒ€์ฆ",
+                "cluster/requestfailed": "์š”์ฒญ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค",
+                "cluster/resume": "์žฌ๊ฐœ",
+                "cluster/revoke": "์ทจ์†Œ",
+                "cluster/route": "๊ฒฝ๋กœ",
+                "cluster/route/direct": "์ง์ ‘",
+                "cluster/route/none": "๊ฒฝ๋กœ ์—†์Œ",
+                "cluster/route/relay": "{0} ๊ฒฝ์œ  ์ค‘๊ณ„",
+                "cluster/route/tunnel": "ํ„ฐ๋„",
+                "cluster/save": "์ €์žฅ",
+                "cluster/saved": "์ €์žฅํ–ˆ์Šต๋‹ˆ๋‹ค",
+                "cluster/sched/cpu": "CPU ์—ฌ์œ ",
+                "cluster/sched/cpu/hint": "ํ•œ๊ฐ€ํ•œ ํ”„๋กœ์„ธ์„œ๋ฅผ ์šฐ์„ ",
+                "cluster/sched/degraded": "์ƒํƒœ",
+                "cluster/sched/degraded/hint": "ํž˜๊ฒจ์›Œํ•˜๋Š” ๋…ธ๋“œ๋ฅผ ํ”ผํ•จ",
+                "cluster/sched/disk": "์—ฌ์œ  ๋””์Šคํฌ",
+                "cluster/sched/disk/hint": "๊ณต๊ฐ„ ์—ฌ์œ ๊ฐ€ ์žˆ๋Š” ๋…ธ๋“œ๋ฅผ ์šฐ์„ ",
+                "cluster/sched/diskfree": "๋””์Šคํฌ ์—ฌ์œ ",
+                "cluster/sched/diversity": "์‚ฌ์ดํŠธ ๋ถ„์‚ฐ",
+                "cluster/sched/diversity/hint": "๋‘ ๋ฒˆ์งธ ์‚ฌ๋ณธ์„ ์ฒซ ๋ฒˆ์งธ์™€ ๋ฉ€๋ฆฌ ๋‘์–ด ํ•œ ์‚ฌ์ดํŠธ๊ฐ€ ๋ฉˆ์ถฐ๋„ ๋ฐ์ดํ„ฐ๋ฅผ ์žƒ์ง€ ์•Š๊ฒŒ ํ•จ",
+                "cluster/sched/explain": "์ž‘์—…์ด ์™œ ๊ฑฐ๊ธฐ์— ๋ฐฐ์ •๋˜์—ˆ๋‚˜์š”?",
+                "cluster/sched/explainbtn": "์„ค๋ช…",
+                "cluster/sched/explainhint": "ํด๋Ÿฌ์Šคํ„ฐ ์ž‘์—… ํŽ˜์ด์ง€์—์„œ ์ž‘์—… ID๋ฅผ ๋ณต์‚ฌํ•˜๋ฉด ๋ฐฐ์ • ๋‹น์‹œ์˜ ์ˆœ์œ„๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
+                "cluster/sched/factor/cpu free": "CPU ์—ฌ์œ ",
+                "cluster/sched/factor/degraded": "์„ฑ๋Šฅ ์ €ํ•˜",
+                "cluster/sched/factor/disk free": "๋””์Šคํฌ ์—ฌ์œ ",
+                "cluster/sched/factor/locality": "๋ฐ์ดํ„ฐ ์œ„์น˜",
+                "cluster/sched/factor/memory free": "๋ฉ”๋ชจ๋ฆฌ ์—ฌ์œ ",
+                "cluster/sched/factor/network distance": "๋„คํŠธ์›Œํฌ ๊ฑฐ๋ฆฌ",
+                "cluster/sched/factor/preferred features": "์„ ํ˜ธ ๊ธฐ๋Šฅ",
+                "cluster/sched/factor/queue depth": "๋Œ€๊ธฐ์—ด ๊ธธ์ด",
+                "cluster/sched/factor/site diversity": "์‚ฌ์ดํŠธ ๋ถ„์‚ฐ",
+                "cluster/sched/features": "๊ธฐ๋Šฅ {0}๊ฐœ",
+                "cluster/sched/from": "์ถœ๋ฐœ / ๋„์ฐฉ",
+                "cluster/sched/here": "๋กœ์ปฌ",
+                "cluster/sched/hint": "์ž‘์—…, ์ƒˆ ํŒŒ์ผ, ์ถ”๊ฐ€ ์‚ฌ๋ณธ์€ ์ ์ˆ˜๊ฐ€ ๊ฐ€์žฅ ๋†’์€ ๋…ธ๋“œ๋กœ ๊ฐ‘๋‹ˆ๋‹ค. ๊ฐ€์ค‘์น˜๋ฅผ ๋†’์ด๋ฉด ํ•ด๋‹น ์š”์†Œ๊ฐ€ ๋” ์ค‘์š”ํ•ด์ง‘๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ ์ ์ˆ˜๋Š” ํด๋Ÿฌ์Šคํ„ฐ ์ •๋ณด์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.",
+                "cluster/sched/infohint": "ํด๋Ÿฌ์Šคํ„ฐ ์„ค์ •์˜ ๊ฐ€์ค‘์น˜์— ๋”ฐ๋ฅธ ๊ฐ ๋…ธ๋“œ์˜ ํ˜„์žฌ ์ ์ˆ˜์ž…๋‹ˆ๋‹ค. ์ž‘์—…, ์ƒˆ ํŒŒ์ผ, ์ถ”๊ฐ€ ์‚ฌ๋ณธ์€ ๊ฐ€์žฅ ๋†’์€ ์ ์ˆ˜๋กœ ๊ฐ‘๋‹ˆ๋‹ค.",
+                "cluster/sched/latency": "๋„คํŠธ์›Œํฌ ๊ฑฐ๋ฆฌ",
+                "cluster/sched/latency/hint": "๋ฐ์ดํ„ฐ์—์„œ ๋จผ ๋…ธ๋“œ๋ฅผ ํ”ผํ•จ",
+                "cluster/sched/locality": "๋ฐ์ดํ„ฐ ์œ„์น˜",
+                "cluster/sched/locality/hint": "์ด๋ฏธ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง„ ๋…ธ๋“œ๋ฅผ ์šฐ์„ ",
+                "cluster/sched/matrixhint": "๋…ธ๋“œ ์Œ ์‚ฌ์ด์˜ ์™•๋ณต ์‹œ๊ฐ„(๋ฐ€๋ฆฌ์ดˆ)์ž…๋‹ˆ๋‹ค. ๊ฑฐ๋ฆฌ๋Š” ๋ณดํ†ต ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ๋ฅผ ๋œปํ•˜๋ฏ€๋กœ ํŒŒ์ผ ์‚ฌ๋ณธ์€ ๊ฐ€์žฅ ๋จผ ์Œ์œผ๋กœ ๋ถ„์‚ฐ๋ฉ๋‹ˆ๋‹ค.",
+                "cluster/sched/mem": "๋ฉ”๋ชจ๋ฆฌ ์—ฌ์œ ",
+                "cluster/sched/memory": "์—ฌ์œ  ๋ฉ”๋ชจ๋ฆฌ",
+                "cluster/sched/memory/hint": "RAM ์—ฌ์œ ๊ฐ€ ์žˆ๋Š” ๋…ธ๋“œ๋ฅผ ์šฐ์„ ",
+                "cluster/sched/next": "๋‹ค์Œ ์ž‘์—…์€ {0}์—์„œ ์‹คํ–‰",
+                "cluster/sched/nojob": "์ด ํด๋Ÿฌ์Šคํ„ฐ์— ํ•ด๋‹น ์ž‘์—…์ด ์—†์Šต๋‹ˆ๋‹ค",
+                "cluster/sched/nonodes": "์ ์ˆ˜๋ฅผ ๋งค๊ธธ ๋…ธ๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค",
+                "cluster/sched/ping": "์™•๋ณต ์‹œ๊ฐ„",
+                "cluster/sched/preferred": "์„ ํ˜ธ ๊ธฐ๋Šฅ",
+                "cluster/sched/preferred/hint": "์ž‘์—…์ด ์›ํ•˜๋Š” ์„ ํƒ ๊ธฐ๋Šฅ์„ ๊ฐ€์ง„ ๋…ธ๋“œ๋ฅผ ์šฐ์„ ",
+                "cluster/sched/queue": "๋Œ€๊ธฐ์—ด ๊ธธ์ด",
+                "cluster/sched/queue/hint": "์ด๋ฏธ ๋ฐ”์œ ๋…ธ๋“œ๋ฅผ ํ”ผํ•จ",
+                "cluster/sched/reset": "๊ธฐ๋ณธ๊ฐ’ ๋ณต์›",
+                "cluster/sched/restored": "๊ธฐ๋ณธ ๊ฐ€์ค‘์น˜๋ฅผ ๋ณต์›ํ–ˆ์Šต๋‹ˆ๋‹ค",
+                "cluster/sched/save": "๊ฐ€์ค‘์น˜ ์ €์žฅ",
+                "cluster/sched/saved": "๊ฐ€์ค‘์น˜๋ฅผ ํด๋Ÿฌ์Šคํ„ฐ ์ „์ฒด์— ์ €์žฅํ–ˆ์Šต๋‹ˆ๋‹ค",
+                "cluster/sched/score": "์ ์ˆ˜",
+                "cluster/sched/skipped": "์ œ์™ธ",
+                "cluster/sched/title": "์Šค์ผ€์ค„๋ง",
+                "cluster/sched/unmeasured": "์ธก์ • ์•ˆ ๋จ",
+                "cluster/settings/tagline": "์ด ๋…ธ๋“œ์™€ ํด๋Ÿฌ์Šคํ„ฐ์˜ ํŒŒ์ผ ์ €์žฅ ๋ฐ ์ž‘์—… ์‹คํ–‰ ๋ฐฉ์‹์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค",
+                "cluster/settings/title": "ํด๋Ÿฌ์Šคํ„ฐ ์„ค์ •",
+                "cluster/state": "์ƒํƒœ",
+                "cluster/state/cancelled": "์ทจ์†Œ๋จ",
+                "cluster/state/committed": "๊ธฐ๋ก๋จ",
+                "cluster/state/degraded": "์„ฑ๋Šฅ ์ €ํ•˜",
+                "cluster/state/done": "์™„๋ฃŒ",
+                "cluster/state/draining": "๋“œ๋ ˆ์ธ ์ค‘",
+                "cluster/state/failed": "์‹คํŒจ",
+                "cluster/state/maintenance": "์œ ์ง€ ๋ณด์ˆ˜ ์ค‘",
+                "cluster/state/offline": "์˜คํ”„๋ผ์ธ",
+                "cluster/state/online": "์˜จ๋ผ์ธ",
+                "cluster/state/pending": "๋Œ€๊ธฐ",
+                "cluster/state/queued": "๋Œ€๊ธฐ ์ค‘",
+                "cluster/state/running": "์‹คํ–‰ ์ค‘",
+                "cluster/state/scheduled": "๋ฐฐ์ •๋จ",
+                "cluster/state/stale": "์˜ค๋ž˜๋จ",
+                "cluster/state/succeeded": "์„ฑ๊ณต",
+                "cluster/state/unknown": "์•Œ ์ˆ˜ ์—†์Œ",
+                "cluster/state/verified": "๊ฒ€์ฆ๋จ",
+                "cluster/state/writing": "์“ฐ๋Š” ์ค‘",
+                "cluster/storage": "์ €์žฅ์†Œ",
+                "cluster/testing": "ํ…Œ์ŠคํŠธ ์ค‘...",
+                "cluster/thisnode": "์ด ๋…ธ๋“œ",
+                "cluster/tokenhint": "ํ† ํฐ์€ ๊ณต๊ฐœ URL์ด ์žˆ๋Š” ๋…ธ๋“œ์—์„œ๋งŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๋…ธ๋“œ์˜ ํด๋Ÿฌ์Šคํ„ฐ ์„ค์ •์— ๋ถ™์—ฌ ๋„ฃ์œผ์„ธ์š”.",
+                "cluster/tokenline": "{0}์— ๋งŒ๋ฃŒ ยท {1}ํšŒ ์‚ฌ์šฉ",
+                "cluster/ttl/1h": "1์‹œ๊ฐ„",
+                "cluster/ttl/24h": "24์‹œ๊ฐ„",
+                "cluster/ttl/7d": "7์ผ",
+                "cluster/tunnel": "ํ„ฐ๋„",
+                "cluster/tunnelhere": "์ด ๋…ธ๋“œ๋กœ์˜ ํ„ฐ๋„",
+                "cluster/tunnelvia": "{0} ๊ฒฝ์œ  ํ„ฐ๋„",
+                "cluster/unreachable": "์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†์Œ",
+                "cluster/updated": "{0}์— ์—…๋ฐ์ดํŠธ",
+                "cluster/uptime": "๊ฐ€๋™ ์‹œ๊ฐ„",
+                "cluster/uptimevalue": "{0}์‹œ๊ฐ„ {1}๋ถ„",
+                "cluster/version": "๋ฒ„์ „",
+                "cluster/via": "{0} ๊ฒฝ์œ ",
+                "cluster/vol/add": "์ด ๋…ธ๋“œ์˜ ํด๋” ์ œ๊ณต",
+                "cluster/vol/addbtn": "๋ณผ๋ฅจ ์ถ”๊ฐ€",
+                "cluster/vol/added": "๋ณผ๋ฅจ์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค",
+                "cluster/vol/autoro": "๊ฑฐ์˜ ์ฐฌ ๋ณผ๋ฅจ์„ ์ž๋™์œผ๋กœ ์ฝ๊ธฐ ์ „์šฉ์œผ๋กœ ์„ค์ •",
+                "cluster/vol/autorohint": "๋ชจ๋“  ๋…ธ๋“œ์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ์—ฌ์œ  ๊ณต๊ฐ„์ด 5% ๋ฏธ๋งŒ์ธ ๋ณผ๋ฅจ์€ ์ƒˆ ํŒŒ์ผ์„ ๋ฐ›์ง€ ์•Š๊ณ  7%๋ฅผ ๋„˜์œผ๋ฉด ๋‹ค์‹œ ๋ฐ›์Šต๋‹ˆ๋‹ค. ๋„๋ฉด ๋ณผ๋ฅจ์„ ๊ฐ€๋“ ์ฑ„์šธ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด ๊ธฐ๋Šฅ์œผ๋กœ ์ž ๊ฒผ๋˜ ๋ณผ๋ฅจ์€ 1๋ถ„ ์•ˆ์— ๋‹ค์‹œ ์“ฐ๊ธฐ ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.",
+                "cluster/vol/autorooff": "์ด์ œ ๋ณผ๋ฅจ์„ ๊ฐ€๋“ ์ฑ„์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค",
+                "cluster/vol/autoroon": "๊ฑฐ์˜ ์ฐฌ ๋ณผ๋ฅจ์€ ์ฝ๊ธฐ ์ „์šฉ์ด ๋ฉ๋‹ˆ๋‹ค",
+                "cluster/vol/confirmevac": "\"{0}\"์˜ ๋ชจ๋“  ํŒŒ์ผ์„ ์˜ฎ๊ธฐ์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? ๋ณผ๋ฅจ์€ ์ฝ๊ธฐ ์ „์šฉ์ด ๋˜๋ฉฐ ๋ชจ๋“  ์‚ฌ๋ณธ์ด ๋‹ค๋ฅธ ๊ณณ์— ์ƒ๊ธฐ๋ฉด ํ‡ด์—ญํ•ฉ๋‹ˆ๋‹ค.",
+                "cluster/vol/confirmremove": "\"{0}\" ์ œ๊ณต์„ ์ค‘์ง€ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? ํŒŒ์ผ์€ ๋””์Šคํฌ์— ๋‚จ์ง€๋งŒ ๊ทธ๊ณณ์—๋งŒ ์žˆ๋Š” ์‚ฌ๋ณธ์€ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.",
+                "cluster/vol/evacstarted": "๋น„์šฐ๊ธฐ ์‹œ์ž‘: ์˜ฎ๊ธธ ํŒŒ์ผ {0}๊ฐœ",
+                "cluster/vol/evacuate": "๋น„์šฐ๊ธฐ",
+                "cluster/vol/evacuating": "๋น„์šฐ๋Š” ์ค‘",
+                "cluster/vol/files": "ํŒŒ์ผ",
+                "cluster/vol/full": "๊ฑฐ์˜ ์ฐธ",
+                "cluster/vol/hint": "cluster:/์— ์“ด ํŒŒ์ผ์€ ์ œ๊ณต๋œ ํด๋” ์•ˆ์— ์ผ๋ฐ˜ ํŒŒ์ผ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ํด๋”์— ์›๋ž˜ ์žˆ๋˜ ํŒŒ์ผ์€ ๋‹ค์Œ ์žฌ๊ฒ€์ƒ‰ ๋•Œ ๋„ค์ž„์ŠคํŽ˜์ด์Šค์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ํด๋Ÿฌ์Šคํ„ฐ์— ๋ณผ๋ฅจ์ด ํ•˜๋‚˜ ์ด์ƒ ์žˆ์œผ๋ฉด ํŒŒ์ผ ๊ด€๋ฆฌ์ž์— cluster:/ ๋“œ๋ผ์ด๋ธŒ๊ฐ€ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.",
+                "cluster/vol/none": "์•„์ง ๋ณผ๋ฅจ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ด ๋…ธ๋“œ์˜ ํด๋”๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด cluster:/์— ํŒŒ์ผ์„ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค",
+                "cluster/vol/readonly": "์ฝ๊ธฐ ์ „์šฉ",
+                "cluster/vol/rescan": "์ด ๋…ธ๋“œ ์žฌ๊ฒ€์ƒ‰",
+                "cluster/vol/rescanned": "์žฌ๊ฒ€์ƒ‰ ์™„๋ฃŒ: ํฌํ•จ {0}๊ฐœ, ์˜ค๋ž˜๋จ {1}๊ฐœ",
+                "cluster/vol/ro": "์ฝ๊ธฐ ์ „์šฉ",
+                "cluster/vol/space": "์—ฌ์œ  / ์ „์ฒด",
+                "cluster/vol/summary": "๋ณผ๋ฅจ {0}๊ฐœ, ์—ฌ์œ  {1}",
+                "cluster/vol/volume": "๋ณผ๋ฅจ",
+                "cluster/vol/writable": "์“ฐ๊ธฐ ๊ฐ€๋Šฅ์œผ๋กœ",
+                "cluster/waitinghost": "์—ฐ๊ฒฐ ๊ฐ€๋Šฅํ•œ ๊ตฌ์„ฑ์›์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ค‘",
+                "cluster/when": "์‹œ๊ฐ„",
+                "cluster/willtunnel": "ํด๋Ÿฌ์Šคํ„ฐ์— ์ฐธ๊ฐ€ํ•˜๋ฉด ๊ตฌ์„ฑ์›์„ ํ†ตํ•ด ํ„ฐ๋„์„ ์—ฝ๋‹ˆ๋‹ค",
+                "cluster/you": "๋‚˜",
+                "msg/AGI gateway not ready": "AGI ๊ฒŒ์ดํŠธ์›จ์ด๊ฐ€ ์ค€๋น„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค",
+                "msg/Invalid MAC address": "MAC ์ฃผ์†Œ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msg/POST required": "POST๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/URL must start with http:// or https://": "URL์€ http:// ๋˜๋Š” https://๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/a job id is required": "์ž‘์—… ID๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/a map/reduce job needs a dataset pattern": "map/reduce ์ž‘์—…์—๋Š” ๋ฐ์ดํ„ฐ ์„ธํŠธ ํŒจํ„ด์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/a map/reduce script must define mapper(files, emit) and reducer(key, values)": "map/reduce ์Šคํฌ๋ฆฝํŠธ์—๋Š” mapper(files, emit)์™€ reducer(key, values)๊ฐ€ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/advertise URL must start with http:// or https://": "๊ณต๊ฐœ URL์€ http:// ๋˜๋Š” https://๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/args must be valid JSON": "์ธ์ˆ˜๋Š” ์˜ฌ๋ฐ”๋ฅธ JSON์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/cannot remove the namespace root": "๋„ค์ž„์ŠคํŽ˜์ด์Šค ๋ฃจํŠธ๋Š” ์ œ๊ฑฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/checksum mismatch": "์ฒดํฌ์„ฌ์ด ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msg/chunk checksum mismatch": "์ฒญํฌ ์ฒดํฌ์„ฌ์ด ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msg/chunk exceeds file size": "์ฒญํฌ๊ฐ€ ํŒŒ์ผ ํฌ๊ธฐ๋ฅผ ๋„˜์Šต๋‹ˆ๋‹ค",
+                "msg/chunk index out of range": "์ฒญํฌ ๋ฒˆํ˜ธ๊ฐ€ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚ฌ์Šต๋‹ˆ๋‹ค",
+                "msg/chunk size invalid": "์ฒญํฌ ํฌ๊ธฐ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msg/cluster jobs not available": "ํด๋Ÿฌ์Šคํ„ฐ ์ž‘์—…์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/cluster name cannot be empty": "ํด๋Ÿฌ์Šคํ„ฐ ์ด๋ฆ„์€ ๋น„์›Œ ๋‘˜ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/cluster name required": "ํด๋Ÿฌ์Šคํ„ฐ ์ด๋ฆ„์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/directory not empty": "ํด๋”๊ฐ€ ๋น„์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msg/disk usage not supported on this platform": "์ด ํ”Œ๋žซํผ์—์„œ๋Š” ๋””์Šคํฌ ์‚ฌ์šฉ๋Ÿ‰์„ ํ™•์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/drive required": "๋“œ๋ผ์ด๋ธŒ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/empty response": "์‘๋‹ต์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค",
+                "msg/enabled must be true or false": "enabled๋Š” true ๋˜๋Š” false์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/event bus not available": "์ด๋ฒคํŠธ ๋ฒ„์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/event data too large (64 KB max)": "์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ๊ฐ€ ๋„ˆ๋ฌด ํฝ๋‹ˆ๋‹ค(์ตœ๋Œ€ 64KB)",
+                "msg/event type required": "์ด๋ฒคํŠธ ์œ ํ˜•์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/evict notice is not for this node": "์ œ๊ฑฐ ์•Œ๋ฆผ์ด ์ด ๋…ธ๋“œ์šฉ์ด ์•„๋‹™๋‹ˆ๋‹ค",
+                "msg/file changed since the copy was planned": "๋ณต์‚ฌ๋ฅผ ๊ณ„ํšํ•œ ๋’ค ํŒŒ์ผ์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค",
+                "msg/file no longer exists in the namespace": "ํŒŒ์ผ์ด ๋” ์ด์ƒ ๋„ค์ž„์ŠคํŽ˜์ด์Šค์— ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/file not found on volume": "๋ณผ๋ฅจ์—์„œ ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/folder path required": "ํด๋” ๊ฒฝ๋กœ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/folder required": "ํด๋”๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/hook not found": "ํ›…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/insufficient cpu cores": "CPU ์ฝ”์–ด๊ฐ€ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค",
+                "msg/insufficient memory": "๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค",
+                "msg/invalid append payload": "์ž˜๋ชป๋œ ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค",
+                "msg/invalid batch": "์ž˜๋ชป๋œ ์ผ๊ด„ ์ฒ˜๋ฆฌ์ž…๋‹ˆ๋‹ค",
+                "msg/invalid begin request": "์ž˜๋ชป๋œ ์‹œ์ž‘ ์š”์ฒญ์ž…๋‹ˆ๋‹ค",
+                "msg/invalid entry": "์ž˜๋ชป๋œ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค",
+                "msg/invalid heartbeat": "์ž˜๋ชป๋œ ํ•˜ํŠธ๋น„ํŠธ์ž…๋‹ˆ๋‹ค",
+                "msg/invalid job spec": "์ž˜๋ชป๋œ ์ž‘์—… ์‚ฌ์–‘์ž…๋‹ˆ๋‹ค",
+                "msg/invalid join request": "์ž˜๋ชป๋œ ์ฐธ๊ฐ€ ์š”์ฒญ์ž…๋‹ˆ๋‹ค",
+                "msg/invalid lease": "์ž˜๋ชป๋œ ์ž„๋Œ€์ž…๋‹ˆ๋‹ค",
+                "msg/invalid node URL": "๋…ธ๋“œ URL์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msg/invalid node public key": "๋…ธ๋“œ ๊ณต๊ฐœ ํ‚ค๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msg/invalid node record": "์ž˜๋ชป๋œ ๋…ธ๋“œ ๊ธฐ๋ก์ž…๋‹ˆ๋‹ค",
+                "msg/invalid range": "์ž˜๋ชป๋œ ๋ฒ”์œ„์ž…๋‹ˆ๋‹ค",
+                "msg/invalid rename": "์ž˜๋ชป๋œ ์ด๋ฆ„ ๋ณ€๊ฒฝ์ž…๋‹ˆ๋‹ค",
+                "msg/invalid report": "์ž˜๋ชป๋œ ๋ณด๊ณ ์ž…๋‹ˆ๋‹ค",
+                "msg/invalid request": "์ž˜๋ชป๋œ ์š”์ฒญ์ž…๋‹ˆ๋‹ค",
+                "msg/invalid sync payload": "์ž˜๋ชป๋œ ๋™๊ธฐํ™” ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค",
+                "msg/invalid task": "์ž˜๋ชป๋œ ์ž‘์—…์ž…๋‹ˆ๋‹ค",
+                "msg/invalid username": "์‚ฌ์šฉ์ž ์ด๋ฆ„์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msg/is a directory": "ํด๋”์ž…๋‹ˆ๋‹ค",
+                "msg/job cancelled": "์ž‘์—…์ด ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค",
+                "msg/job id required": "์ž‘์—… ID๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/job is limited to other nodes": "์ด ์ž‘์—…์€ ๋‹ค๋ฅธ ๋…ธ๋“œ๋กœ ์ œํ•œ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค",
+                "msg/job not found": "์ž‘์—…์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/job owner is required": "์ž‘์—… ์†Œ์œ ์ž๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/job script is empty": "์ž‘์—… ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค",
+                "msg/job timed out": "์ž‘์—… ์‹œ๊ฐ„์ด ์ดˆ๊ณผ๋˜์—ˆ์Šต๋‹ˆ๋‹ค",
+                "msg/join response does not match the token's cluster": "์ฐธ๊ฐ€ ์‘๋‹ต์ด ํ† ํฐ์˜ ํด๋Ÿฌ์Šคํ„ฐ์™€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msg/join token has an invalid node URL": "์ฐธ๊ฐ€ ํ† ํฐ์˜ ๋…ธ๋“œ URL์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msg/join token is corrupted": "์ฐธ๊ฐ€ ํ† ํฐ์ด ์†์ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค",
+                "msg/join token is for another cluster": "์ฐธ๊ฐ€ ํ† ํฐ์ด ๋‹ค๋ฅธ ํด๋Ÿฌ์Šคํ„ฐ์šฉ์ž…๋‹ˆ๋‹ค",
+                "msg/join token is incomplete": "์ฐธ๊ฐ€ ํ† ํฐ์ด ๋ถˆ์™„์ „ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/join token is invalid or expired": "์ฐธ๊ฐ€ ํ† ํฐ์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š๊ฑฐ๋‚˜ ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค",
+                "msg/join token required": "์ฐธ๊ฐ€ ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/metadata store not available": "๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/no cluster volume available; add one in System Settings > Cluster Settings > Storage": "์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํด๋Ÿฌ์Šคํ„ฐ ๋ณผ๋ฅจ์ด ์—†์Šต๋‹ˆ๋‹ค. \"์‹œ์Šคํ…œ ์„ค์ • > ํด๋Ÿฌ์Šคํ„ฐ ์„ค์ • > ์ €์žฅ์†Œ\"์—์„œ ์ถ”๊ฐ€ํ•˜์„ธ์š”",
+                "msg/no healthy copy of this file is reachable right now": "ํ˜„์žฌ ์ด ํŒŒ์ผ์˜ ์ •์ƒ ์‚ฌ๋ณธ์— ์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/no node is available": "์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋…ธ๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/node already has enough copies in flight": "์ด ๋…ธ๋“œ์—์„œ ์ด๋ฏธ ์ถฉ๋ถ„ํ•œ ๋ณต์‚ฌ๊ฐ€ ์ง„ํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค",
+                "msg/node id is required": "๋…ธ๋“œ ID๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/node id required": "๋…ธ๋“œ ID๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/node is DEGRADED": "๋…ธ๋“œ ์„ฑ๋Šฅ์ด ์ €ํ•˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค",
+                "msg/node is DRAINING": "๋…ธ๋“œ๊ฐ€ ๋“œ๋ ˆ์ธ ์ค‘์ž…๋‹ˆ๋‹ค",
+                "msg/node is MAINTENANCE": "๋…ธ๋“œ๊ฐ€ ์œ ์ง€ ๋ณด์ˆ˜ ์ค‘์ž…๋‹ˆ๋‹ค",
+                "msg/node is OFFLINE": "๋…ธ๋“œ๊ฐ€ ์˜คํ”„๋ผ์ธ์ž…๋‹ˆ๋‹ค",
+                "msg/node is UNKNOWN": "๋…ธ๋“œ ์ƒํƒœ๋ฅผ ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/node is degraded": "๋…ธ๋“œ ์„ฑ๋Šฅ์ด ์ €ํ•˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค",
+                "msg/node is draining": "๋…ธ๋“œ๊ฐ€ ๋“œ๋ ˆ์ธ ์ค‘์ž…๋‹ˆ๋‹ค",
+                "msg/node is maintenance": "๋…ธ๋“œ๊ฐ€ ์œ ์ง€ ๋ณด์ˆ˜ ์ค‘์ž…๋‹ˆ๋‹ค",
+                "msg/node is not available": "๋…ธ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/node is offline": "๋…ธ๋“œ๊ฐ€ ์˜คํ”„๋ผ์ธ์ž…๋‹ˆ๋‹ค",
+                "msg/node is unknown": "๋…ธ๋“œ ์ƒํƒœ๋ฅผ ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/node lost while running the job": "์ž‘์—… ์‹คํ–‰ ์ค‘ ๋…ธ๋“œ ์—ฐ๊ฒฐ์ด ๋Š๊ฒผ์Šต๋‹ˆ๋‹ค",
+                "msg/node name cannot be empty": "๋…ธ๋“œ ์ด๋ฆ„์€ ๋น„์›Œ ๋‘˜ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/node not found": "๋…ธ๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/node refused this job earlier": "์ด ๋…ธ๋“œ๊ฐ€ ์ด์ „์— ์ด ์ž‘์—…์„ ๊ฑฐ๋ถ€ํ–ˆ์Šต๋‹ˆ๋‹ค",
+                "msg/not a directory": "ํด๋”๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค",
+                "msg/not a file": "ํŒŒ์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค",
+                "msg/not an ArozOS join token": "ArozOS ์ฐธ๊ฐ€ ํ† ํฐ์ด ์•„๋‹™๋‹ˆ๋‹ค",
+                "msg/not in a cluster": "ํด๋Ÿฌ์Šคํ„ฐ์— ์†ํ•ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msg/not logged in": "๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค",
+                "msg/offset beyond end of file": "์˜คํ”„์…‹์ด ํŒŒ์ผ ๋์„ ๋„˜์—ˆ์Šต๋‹ˆ๋‹ค",
+                "msg/only the metadata leader assigns jobs": "๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ฆฌ๋”๋งŒ ์ž‘์—…์„ ๋ฐฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค",
+                "msg/only the metadata leader assigns replication tasks": "๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ฆฌ๋”๋งŒ ๋ณต์ œ ์ž‘์—…์„ ๋ฐฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค",
+                "msg/owner, script and at least one event type are required": "์†Œ์œ ์ž, ์Šคํฌ๋ฆฝํŠธ, ์ด๋ฒคํŠธ ์œ ํ˜• ํ•˜๋‚˜ ์ด์ƒ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/path already exists": "๊ฒฝ๋กœ๊ฐ€ ์ด๋ฏธ ์žˆ์Šต๋‹ˆ๋‹ค",
+                "msg/path escapes the volume": "๊ฒฝ๋กœ๊ฐ€ ๋ณผ๋ฅจ ๋ฐ–์„ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค",
+                "msg/path not found in the cluster namespace": "ํด๋Ÿฌ์Šคํ„ฐ ๋„ค์ž„์ŠคํŽ˜์ด์Šค์—์„œ ๊ฒฝ๋กœ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/path required": "๊ฒฝ๋กœ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/peer has no reachable endpoint": "์ƒ๋Œ€ ๋…ธ๋“œ์— ์—ฐ๊ฒฐ ๊ฐ€๋Šฅํ•œ ์—”๋“œํฌ์ธํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/permission denied": "๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/previous node stopped reporting": "์ด์ „ ๋…ธ๋“œ๊ฐ€ ๋ณด๊ณ ๋ฅผ ๋ฉˆ์ท„์Šต๋‹ˆ๋‹ค",
+                "msg/refusing to delete the volume root": "๋ณผ๋ฅจ ๋ฃจํŠธ๋Š” ์‚ญ์ œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/refusing to drop the last healthy copy": "๋งˆ์ง€๋ง‰ ์ •์ƒ ์‚ฌ๋ณธ์€ ์‚ญ์ œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/replicas must be between 0 and 16": "์‚ฌ๋ณธ ์ˆ˜๋Š” 0์—์„œ 16 ์‚ฌ์ด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/replicas must be between 1 and 16": "์‚ฌ๋ณธ ์ˆ˜๋Š” 1์—์„œ 16 ์‚ฌ์ด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/request is not signed": "์š”์ฒญ์— ์„œ๋ช…์ด ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/request signature invalid": "์š”์ฒญ ์„œ๋ช…์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msg/request targets another cluster": "์š”์ฒญ ๋Œ€์ƒ์ด ๋‹ค๋ฅธ ํด๋Ÿฌ์Šคํ„ฐ์ž…๋‹ˆ๋‹ค",
+                "msg/request timestamp outside the accepted window": "์š”์ฒญ ํƒ€์ž„์Šคํƒฌํ”„๊ฐ€ ํ—ˆ์šฉ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚ฌ์Šต๋‹ˆ๋‹ค(๋…ธ๋“œ ์‹œ๊ณ„๋ฅผ ํ™•์ธํ•˜์„ธ์š”)",
+                "msg/requested log range was compacted, take a snapshot": "์š”์ฒญํ•œ ๋กœ๊ทธ ๋ฒ”์œ„๊ฐ€ ์••์ถ•๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์Šค๋ƒ…์ƒท์„ ๋ฐ›์œผ์„ธ์š”",
+                "msg/script path required": "์Šคํฌ๋ฆฝํŠธ ๊ฒฝ๋กœ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/sender is not a member of this cluster": "๋ณด๋‚ธ ๋…ธ๋“œ๊ฐ€ ์ด ํด๋Ÿฌ์Šคํ„ฐ์˜ ๊ตฌ์„ฑ์›์ด ์•„๋‹™๋‹ˆ๋‹ค",
+                "msg/shutting down": "์ข…๋ฃŒํ•˜๋Š” ์ค‘",
+                "msg/source copy is not readable": "์›๋ณธ ์‚ฌ๋ณธ์„ ์ฝ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/target node is not connected through a tunnel on this node": "๋Œ€์ƒ ๋…ธ๋“œ๊ฐ€ ์ด ๋…ธ๋“œ์˜ ํ„ฐ๋„๋กœ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msg/task is for another node": "์ด ์ž‘์—…์€ ๋‹ค๋ฅธ ๋…ธ๋“œ์šฉ์ž…๋‹ˆ๋‹ค",
+                "msg/the URL answered but it is not an ArozOS cluster endpoint": "URL์ด ์‘๋‹ตํ–ˆ์ง€๋งŒ ArozOS ํด๋Ÿฌ์Šคํ„ฐ ์—”๋“œํฌ์ธํŠธ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค",
+                "msg/the job has no dataset pattern": "์ด ์ž‘์—…์—๋Š” ๋ฐ์ดํ„ฐ ์„ธํŠธ ํŒจํ„ด์ด ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/the job owner has no account on this node": "์ด ๋…ธ๋“œ์— ์ž‘์—… ์†Œ์œ ์ž์˜ ๊ณ„์ •์ด ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/the mappers emitted more than 8 MB; narrow the dataset or emit fewer values": "mapper ์ถœ๋ ฅ์ด 8MB๋ฅผ ๋„˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ์„ธํŠธ๋ฅผ ์ค„์ด๊ฑฐ๋‚˜ ์ถœ๋ ฅ ๊ฐ’์„ ์ค„์ด์„ธ์š”",
+                "msg/the user root itself cannot be a cluster volume; pick a sub folder such as /cluster": "์‚ฌ์šฉ์ž ๋ฃจํŠธ ์ž์ฒด๋Š” ํด๋Ÿฌ์Šคํ„ฐ ๋ณผ๋ฅจ์ด ๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. /cluster ๊ฐ™์€ ํ•˜์œ„ ํด๋”๋ฅผ ๊ณ ๋ฅด์„ธ์š”",
+                "msg/this folder is already a cluster volume": "์ด ํด๋”๋Š” ์ด๋ฏธ ํด๋Ÿฌ์Šคํ„ฐ ๋ณผ๋ฅจ์ž…๋‹ˆ๋‹ค",
+                "msg/this job script defines no run(job) function": "์ด ์ž‘์—… ์Šคํฌ๋ฆฝํŠธ์— run(job) ํ•จ์ˆ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/this node cannot execute jobs": "์ด ๋…ธ๋“œ๋Š” ์ž‘์—…์„ ์‹คํ–‰ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/this node has no advertised URL; generate the join token on a node that other nodes can reach": "์ด ๋…ธ๋“œ์—๋Š” ๊ณต๊ฐœ URL์ด ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๋…ธ๋“œ๊ฐ€ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๋…ธ๋“œ์—์„œ ์ฐธ๊ฐ€ ํ† ํฐ์„ ์ƒ์„ฑํ•˜์„ธ์š”",
+                "msg/this node is already part of a cluster, leave it first": "์ด ๋…ธ๋“œ๋Š” ์ด๋ฏธ ํด๋Ÿฌ์Šคํ„ฐ์— ์†ํ•ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋จผ์ € ๋‚˜๊ฐ€์„ธ์š”",
+                "msg/this node is not part of a cluster": "์ด ๋…ธ๋“œ๋Š” ํด๋Ÿฌ์Šคํ„ฐ์— ์†ํ•ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msg/this node is not the identity origin of the cluster": "์ด ๋…ธ๋“œ๋Š” ํด๋Ÿฌ์Šคํ„ฐ์˜ ID ์›๋ณธ์ด ์•„๋‹™๋‹ˆ๋‹ค",
+                "msg/this node is not the metadata leader": "์ด ๋…ธ๋“œ๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ฆฌ๋”๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค",
+                "msg/timed out waiting for the job to finish": "์ž‘์—… ์™„๋ฃŒ ๋Œ€๊ธฐ ์‹œ๊ฐ„์ด ์ดˆ๊ณผ๋˜์—ˆ์Šต๋‹ˆ๋‹ค",
+                "msg/token id required": "ํ† ํฐ ID๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/token not found": "ํ† ํฐ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/tunnel closed": "ํ„ฐ๋„์ด ๋‹ซํ˜”์Šต๋‹ˆ๋‹ค",
+                "msg/unknown upload session": "์•Œ ์ˆ˜ ์—†๋Š” ์—…๋กœ๋“œ ์„ธ์…˜์ž…๋‹ˆ๋‹ค",
+                "msg/unreachable": "์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†์Œ",
+                "msg/unsupported node state": "์ง€์›ํ•˜์ง€ ์•Š๋Š” ๋…ธ๋“œ ์ƒํƒœ์ž…๋‹ˆ๋‹ค",
+                "msg/upload incomplete": "์—…๋กœ๋“œ๊ฐ€ ์™„๋ฃŒ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค",
+                "msg/url required": "URL์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/use leave to remove this node": "์ด ๋…ธ๋“œ๋ฅผ ์ œ๊ฑฐํ•˜๋ ค๋ฉด \"ํด๋Ÿฌ์Šคํ„ฐ ๋‚˜๊ฐ€๊ธฐ\"๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”",
+                "msg/user assertion expired": "์‚ฌ์šฉ์ž ์–ด์„ค์…˜์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค",
+                "msg/user assertion invalid": "์‚ฌ์šฉ์ž ์–ด์„ค์…˜์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msg/user not found on the identity origin": "ID ์›๋ณธ์—์„œ ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/volume id required": "๋ณผ๋ฅจ ID๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
+                "msg/volume is not on this node": "๋ณผ๋ฅจ์ด ์ด ๋…ธ๋“œ์— ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/volume not found": "๋ณผ๋ฅจ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msg/volume not found on this node": "์ด ๋…ธ๋“œ์—์„œ ๋ณผ๋ฅจ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
+                "msgp/architecture {0} not allowed": "์•„ํ‚คํ…์ฒ˜ {0}์€(๋Š”) ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msgp/cancelled by {0}": "{0}์ด(๊ฐ€) ์ทจ์†Œํ•จ",
+                "msgp/could not assemble the reduce input: {0}": "reduce ์ž…๋ ฅ์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {0}",
+                "msgp/could not create a map task: {0}": "map ์ž‘์—…์„ ๋งŒ๋“ค ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {0}",
+                "msgp/could not create the reduce task: {0}": "reduce ์ž‘์—…์„ ๋งŒ๋“ค ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {0}",
+                "msgp/drive {0} is not a local drive on this node": "๋“œ๋ผ์ด๋ธŒ {0}์€(๋Š”) ์ด ๋…ธ๋“œ์˜ ๋กœ์ปฌ ๋“œ๋ผ์ด๋ธŒ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค",
+                "msgp/hook script not found: {0}": "ํ›… ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {0}",
+                "msgp/invalid join response from {0}": "{0}์˜ ์ฐธ๊ฐ€ ์‘๋‹ต์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msgp/invalid node key encoding: {0}": "๋…ธ๋“œ ํ‚ค ์ธ์ฝ”๋”ฉ์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค: {0}",
+                "msgp/invalid public key encoding: {0}": "๊ณต๊ฐœ ํ‚ค ์ธ์ฝ”๋”ฉ์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค: {0}",
+                "msgp/job timed out after {0}s": "์ž‘์—…์ด {0}์ดˆ ํ›„ ์‹œ๊ฐ„ ์ดˆ๊ณผ๋˜์—ˆ์Šต๋‹ˆ๋‹ค",
+                "msgp/local drive {0} is not mounted": "๋กœ์ปฌ ๋“œ๋ผ์ด๋ธŒ {0}์ด(๊ฐ€) ๋งˆ์šดํŠธ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค",
+                "msgp/missing feature {0}": "๊ธฐ๋Šฅ {0}์ด(๊ฐ€) ์—†์Šต๋‹ˆ๋‹ค",
+                "msgp/no cluster volume available; add one in System Settings > Cluster Settings > Storage ({0})": "์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํด๋Ÿฌ์Šคํ„ฐ ๋ณผ๋ฅจ์ด ์—†์Šต๋‹ˆ๋‹ค. \"์‹œ์Šคํ…œ ์„ค์ • > ํด๋Ÿฌ์Šคํ„ฐ ์„ค์ • > ์ €์žฅ์†Œ\"์—์„œ ์ถ”๊ฐ€ํ•˜์„ธ์š”({0})",
+                "msgp/no reachable copy of: {0}": "๋‹ค์Œ ํŒŒ์ผ์˜ ์‚ฌ๋ณธ์— ์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {0}",
+                "msgp/node {0} could not take the job: {1}": "๋…ธ๋“œ {0}์ด(๊ฐ€) ์ž‘์—…์„ ๋ฐ›์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค: {1}",
+                "msgp/os {0} not allowed": "์šด์˜ ์ฒด์ œ {0}์€(๋Š”) ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
+                "msgp/permission denied: {0}": "๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค: {0}",
+                "msgp/remote node returned {0}: {1}": "์›๊ฒฉ ๋…ธ๋“œ๊ฐ€ {0}์„(๋ฅผ) ๋ฐ˜ํ™˜ํ–ˆ์Šต๋‹ˆ๋‹ค: {1}",
+                "msgp/script not found: {0}": "์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {0}",
+                "msgp/this volume holds the only copy of {0} file(s); evacuate it first": "์ด ๋ณผ๋ฅจ์— ํŒŒ์ผ {0}๊ฐœ์˜ ์œ ์ผํ•œ ์‚ฌ๋ณธ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋จผ์ € ๋น„์šฐ์„ธ์š”",
+                "msgp/unable to reach {0}: {1}": "{0}์— ์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {1}",
+                "msgp/unreachable: {0}": "์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†์Œ: {0}",
+                "msgp/unsupported node URL scheme: {0}": "์ง€์›ํ•˜์ง€ ์•Š๋Š” ๋…ธ๋“œ URL ์Šคํ‚ด์ž…๋‹ˆ๋‹ค: {0}",
+                "msgp/weight {0} must be a number": "๊ฐ€์ค‘์น˜ {0}์€(๋Š”) ์ˆซ์ž์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค",
+                "msgp/weight {0} must be between 0 and 1": "๊ฐ€์ค‘์น˜ {0}์€(๋Š”) 0๊ณผ 1 ์‚ฌ์ด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค",
+                "msgp/{0} produced an unreadable result": "{0}์ด(๊ฐ€) ์ฝ์„ ์ˆ˜ ์—†๋Š” ๊ฒฐ๊ณผ๋ฅผ ๋ƒˆ์Šต๋‹ˆ๋‹ค"
+            },
+            "placeholder": {
+                "Name (optional)": "์ด๋ฆ„(์„ ํƒ)",
+                "job id": "์ž‘์—… ID",
+                "Nightly index": "์•ผ๊ฐ„ ์ƒ‰์ธ",
+                "50 per task": "์ž‘์—…๋‹น 50๊ฐœ"
+            },
+            "titles": {
+                "Re-detect capabilities": "๊ธฐ๋Šฅ ๋‹ค์‹œ ๊ฐ์ง€"
+            }
+        }
+    }
+}

+ 21 - 0
src/web/SystemAO/locale/system_settings.json

@@ -57,6 +57,9 @@
                 "tab/cluster/Neighbourhood": "็ถฒ่ทฏไธŠ็š„่Šณ้„ฐ",
                 "tab/cluster/Cluster List": "้›†็พค็ฎก็†",
                 "tab/cluster/Cluster Toolbox": "้›†็พคๅทฅๅ…ท",
+                "tab/cluster/Cluster Settings": "ๅข้›†่จญๅฎš",
+                "tab/cluster/Cluster Info": "ๅข้›†่ณ‡่จŠ",
+                "tab/cluster/Cluster Jobs": "ๅข้›†ๅทฅไฝœ",
                 "tab/security/Connection Log": "้€ฃๆŽฅ่จ˜้Œ„",
                 "tab/security/Access Control": "ๅญ˜ๅ–็ฎก็†",
                 "tab/security/OAuth": "OAuth ่จญๅฎš",
@@ -178,6 +181,9 @@
                 "tab/cluster/Neighbourhood": "็ถฒ่ทฏไธŠ็š„่Šณ้„ฐ",
                 "tab/cluster/Cluster List": "้›†็พค็ฎก็†",
                 "tab/cluster/Cluster Toolbox": "้›†็พคๅทฅๅ…ท",
+                "tab/cluster/Cluster Settings": "ๅข้›†่จญๅฎš",
+                "tab/cluster/Cluster Info": "ๅข้›†่ณ‡่จŠ",
+                "tab/cluster/Cluster Jobs": "ๅข้›†ๅทฅไฝœ",
                 "tab/security/Connection Log": "้€ฃๆŽฅ่จ˜้Œ„",
                 "tab/security/Access Control": "ๅญ˜ๅ–็ฎก็†",
                 "tab/security/OAuth": "OAuth ่จญๅฎš",
@@ -298,6 +304,9 @@
                 "tab/cluster/Neighbourhood": "็ฝ‘็ปœ้‚ปๅฑ…",
                 "tab/cluster/Cluster List": "้›†็พค็ฎก็†",
                 "tab/cluster/Cluster Toolbox": "้›†็พคๅทฅๅ…ท",
+                "tab/cluster/Cluster Settings": "้›†็พค่ฎพ็ฝฎ",
+                "tab/cluster/Cluster Info": "้›†็พคไฟกๆฏ",
+                "tab/cluster/Cluster Jobs": "้›†็พคไปปๅŠก",
                 "tab/security/Connection Log": "่ฟžๆŽฅ่ฎฐๅฝ•",
                 "tab/security/Access Control": "่ฎฟ้—ฎ็ฎก็†",
                 "tab/security/OAuth": "OAuth ่ฎพๅฎš",
@@ -419,6 +428,9 @@
                 "tab/cluster/Neighbourhood": "Network Neighborhood",
                 "tab/cluster/Cluster List": "Cluster Management",
                 "tab/cluster/Cluster Toolbox": "Cluster Tools",
+                "tab/cluster/Cluster Settings": "Cluster Settings",
+                "tab/cluster/Cluster Info": "Cluster Info",
+                "tab/cluster/Cluster Jobs": "Cluster Jobs",
                 "tab/security/Connection Log": "Connection Log",
                 "tab/security/Access Control": "Access Control",
                 "tab/security/OAuth": "OAuth Settings",
@@ -539,6 +551,9 @@
                 "tab/cluster/Neighbourhood": "Network Neighborhood",
                 "tab/cluster/Cluster List": "Cluster Management",
                 "tab/cluster/Cluster Toolbox": "Cluster Tools",
+                "tab/cluster/Cluster Settings": "Cluster Settings",
+                "tab/cluster/Cluster Info": "Cluster Info",
+                "tab/cluster/Cluster Jobs": "Cluster Jobs",
                 "tab/security/Connection Log": "Connection Log",
                 "tab/security/Access Control": "Access Control",
                 "tab/security/OAuth": "OAuth Settings",
@@ -659,6 +674,9 @@
                 "tab/cluster/Neighbourhood": "ใƒใƒƒใƒˆใƒฏใƒผใ‚ฏ่ฟ‘้šฃ",
                 "tab/cluster/Cluster List": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผ็ฎก็†",
                 "tab/cluster/Cluster Toolbox": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใƒ„ใƒผใƒซ",
+                "tab/cluster/Cluster Settings": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผ่จญๅฎš",
+                "tab/cluster/Cluster Info": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผๆƒ…ๅ ฑ",
+                "tab/cluster/Cluster Jobs": "ใ‚ฏใƒฉใ‚นใ‚ฟใƒผใ‚ธใƒงใƒ–",
                 "tab/security/Connection Log": "ๆŽฅ็ถšใƒญใ‚ฐ",
                 "tab/security/Access Control": "ใ‚ขใ‚ฏใ‚ปใ‚นๅˆถๅพก",
                 "tab/security/OAuth": "OAuth่จญๅฎš",
@@ -728,6 +746,9 @@
                 "tab/cluster/Neighbourhood": "๋„คํŠธ์›Œํฌ ์ด์›ƒ",
                 "tab/cluster/Cluster List": "ํด๋Ÿฌ์Šคํ„ฐ ๊ด€๋ฆฌ",
                 "tab/cluster/Cluster Toolbox": "ํด๋Ÿฌ์Šคํ„ฐ ๋„๊ตฌ",
+                "tab/cluster/Cluster Settings": "ํด๋Ÿฌ์Šคํ„ฐ ์„ค์ •",
+                "tab/cluster/Cluster Info": "ํด๋Ÿฌ์Šคํ„ฐ ์ •๋ณด",
+                "tab/cluster/Cluster Jobs": "ํด๋Ÿฌ์Šคํ„ฐ ์ž‘์—…",
                 "tab/security/Connection Log": "์—ฐ๊ฒฐ ๋กœ๊ทธ",
                 "tab/security/Access Control": "์•ก์„ธ์Šค ์ œ์–ด",
                 "tab/security/OAuth": "OAuth ์„ค์ •",

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini