diff --git a/plugins/hermes-achievements/dashboard/dist/index.js b/plugins/hermes-achievements/dashboard/dist/index.js
index 5be8a3f1dd1d..ad57212c4620 100644
--- a/plugins/hermes-achievements/dashboard/dist/index.js
+++ b/plugins/hermes-achievements/dashboard/dist/index.js
@@ -42,6 +42,53 @@
return str;
}
+ // Achievement definitions keep English copy as an API fallback. Locales use
+ // the stable ACHIEVEMENTS id directly, so translating a badge never changes
+ // its unlock state or requires a locale-specific scan snapshot.
+ function localizedCategory(t, category) {
+ const categories = t && t.achievements && t.achievements.categories;
+ return (categories && categories[category]) || category;
+ }
+
+ function localizedAchievement(t, achievement) {
+ const catalog = t && t.achievements && t.achievements.catalog;
+ const copy = catalog && catalog[achievement.id];
+ const category = localizedCategory(t, achievement.category);
+ if (!copy && category === achievement.category) return achievement;
+ return Object.assign({}, achievement, {
+ name: (copy && copy.name) || achievement.name,
+ description: (copy && copy.description) || achievement.description,
+ category: category,
+ });
+ }
+
+ function localizedMetric(t, achievement, metric) {
+ const metrics = t && t.achievements && t.achievements.metrics;
+ const spec = achievement.criteria_spec || {};
+ return (metrics && metrics[metric]) || (spec.metric_labels && spec.metric_labels[metric]) || null;
+ }
+
+ function localizedCriteria(t, achievement) {
+ const spec = achievement.criteria_spec;
+ if (!spec || !spec.kind) return achievement.criteria;
+ if (spec.kind === "secret_hidden") {
+ return tx(t, "criteria.secret_hidden", "Secret: exact requirement hidden until Hermes sees the first matching signal. Keep using Hermes across debugging, tools, memory, skills, plugins, and model workflows to reveal it.");
+ }
+ if (spec.kind === "tiered") {
+ const metric = localizedMetric(t, achievement, spec.metric);
+ const ladder = (spec.tiers || []).map(function (tier) { return tier.name + " " + tier.threshold; }).join(", ");
+ return metric && ladder ? tx(t, "criteria.tiered", "Requirement: {metric}. Tier ladder: {ladder}.", { metric: metric, ladder: ladder }) : achievement.criteria;
+ }
+ if (spec.kind === "requirements") {
+ const requirements = (spec.requirements || []).map(function (requirement) {
+ const metric = localizedMetric(t, achievement, requirement.metric);
+ return metric && (metric + " ≥" + (requirement.gte || 1));
+ });
+ return requirements.length && requirements.every(Boolean) ? tx(t, "criteria.requirements", "Requirement: {requirements}.", { requirements: requirements.join("; ") }) : achievement.criteria;
+ }
+ return achievement.criteria;
+ }
+
const LUCIDE = {"flame":"","avalanche":"\n ","nodes":"\n \n \n \n ","rocket":"\n \n \n ","branch":"\n \n \n ","daemon":"\n ","clock":"\n ","warning":"\n \n ","wine":"\n \n \n ","scroll":"\n \n \n ","plug":"\n \n \n \n \n ","lock":"\n \n ","package_skull":"\n \n \n \n ","restart":"\n \n \n ","key":"\n ","colon":"\n ","container":"\n \n \n \n ","melting_clock":"\n \n ","pencil":"\n ","blueprint":"\n \n \n \n ","pixel":"\n \n \n \n ","ship":"\n \n \n \n ","spark_cursor":"\n \n \n \n ","needle":"","hammer_scroll":"\n \n ","anvil":"\n \n \n \n ","crystal":"\n \n ","palace":"\n \n \n \n \n ","dragon":"","antenna":"\n \n \n \n \n \n ","puzzle":"","rewind":"\n ","spiral":"\n \n \n \n ","quote":"\n ","compass":"\n ","browser":"\n \n ","terminal":"\n ","wand":"\n \n \n \n \n \n \n ","folder":"\n \n ","eye":"\n ","wave":"","swap":"\n \n \n ","router":"\n \n \n \n \n ","codex":"\n \n ","prism":"\n \n ","marathon":"\n \n ","calendar":"\n \n \n \n \n \n \n \n \n ","moon":"","cache":"\n \n ","secret":"\n \n "};
const tierClass = function (tier) {
@@ -276,6 +323,7 @@
function ShareDialog({ achievement, onClose }) {
const { t } = useI18n();
+ const displayAchievement = localizedAchievement(t, achievement);
const [status, setStatus] = hooks.useState("rendering"); // rendering | ready | copied | error
const [errorMsg, setErrorMsg] = hooks.useState(null);
const [previewUrl, setPreviewUrl] = hooks.useState(null);
@@ -284,7 +332,7 @@
hooks.useEffect(function () {
let cancelled = false;
let createdUrl = null;
- buildShareImage(achievement).then(function (blob) {
+ buildShareImage(displayAchievement).then(function (blob) {
if (cancelled) return;
blobRef.current = blob;
createdUrl = URL.createObjectURL(blob);
@@ -299,7 +347,7 @@
cancelled = true;
if (createdUrl) URL.revokeObjectURL(createdUrl);
};
- }, [achievement.id]);
+ }, [achievement.id, displayAchievement.name, displayAchievement.description]);
function download() {
if (!blobRef.current) return;
@@ -337,7 +385,7 @@
const tierPart = achievement.tier ? (achievement.tier + " tier ") : "";
const tmpl = tx(t, "share.tweet_text", "Just unlocked {tier_part}\"{name}\" in Hermes Agent ☤", {
tier_part: tierPart,
- name: achievement.name,
+ name: displayAchievement.name,
});
return tmpl + "\n\n@NousResearch · https://hermes-agent.nousresearch.com";
}
@@ -353,12 +401,12 @@
},
React.createElement("div", { className: "ha-share-dialog", role: "dialog", "aria-label": tx(t, "share.dialog_label", "Share achievement") },
React.createElement("div", { className: "ha-share-head" },
- React.createElement("strong", null, tx(t, "share.header", "Share: {name}", { name: achievement.name })),
+ React.createElement("strong", null, tx(t, "share.header", "Share: {name}", { name: displayAchievement.name })),
React.createElement("button", { className: "ha-share-close", onClick: onClose, "aria-label": tx(t, "share.close", "Close") }, "×")
),
React.createElement("div", { className: "ha-share-preview" },
status === "rendering" && React.createElement("div", { className: "ha-share-placeholder" }, tx(t, "share.rendering", "Rendering…")),
- previewUrl && React.createElement("img", { src: previewUrl, alt: tx(t, "share.card_alt", "{name} share card", { name: achievement.name }) })
+ previewUrl && React.createElement("img", { src: previewUrl, alt: tx(t, "share.card_alt", "{name} share card", { name: displayAchievement.name }) })
),
status === "error" && React.createElement("div", { className: "ha-share-error" }, errorMsg || tx(t, "share.error_generic", "Something went wrong.")),
React.createElement("div", { className: "ha-share-actions" },
@@ -491,6 +539,7 @@
function AchievementCard({ achievement }) {
const { t } = useI18n();
+ const displayAchievement = localizedAchievement(t, achievement);
const unlocked = achievement.unlocked;
const progress = achievement.progress || 0;
const pct = achievement.progress_pct || (unlocked ? 100 : 0);
@@ -520,8 +569,8 @@
React.createElement("div", { className: "ha-card-head" },
React.createElement("div", { className: "ha-icon" }, React.createElement(AchievementIcon, { icon: achievement.icon || "secret" })),
React.createElement("div", { className: "ha-card-title-wrap" },
- React.createElement("div", { className: "ha-card-title" }, achievement.name),
- React.createElement("div", { className: "ha-card-category" }, achievement.category)
+ React.createElement("div", { className: "ha-card-title" }, displayAchievement.name),
+ React.createElement("div", { className: "ha-card-category" }, displayAchievement.category)
),
React.createElement("div", { className: "ha-badges" },
React.createElement("span", { className: "ha-state-badge" }, stateLabel),
@@ -530,16 +579,16 @@
className: "ha-share-trigger",
onClick: function () { setShareOpen(true); },
title: tx(t, "card.share_title", "Share this achievement"),
- "aria-label": tx(t, "card.share_label", "Share {name}", { name: achievement.name }),
+ "aria-label": tx(t, "card.share_label", "Share {name}", { name: displayAchievement.name }),
}, tx(t, "card.share_text", "Share"))
)
),
- React.createElement("p", { className: "ha-description" }, achievement.description),
+ React.createElement("p", { className: "ha-description" }, displayAchievement.description),
achievement.criteria && React.createElement("details", { className: "ha-criteria" },
React.createElement("summary", null, state === "secret"
? tx(t, "card.how_to_reveal", "How to reveal")
: tx(t, "card.what_counts", "What counts")),
- React.createElement("p", null, achievement.criteria)
+ React.createElement("p", null, localizedCriteria(t, achievement))
),
React.createElement("div", { className: "ha-evidence-slot" },
achievement.evidence ? React.createElement("div", { className: "ha-evidence" },
@@ -678,7 +727,7 @@
React.createElement(StatCard, { label: tx(t, "stats.discovered", "Discovered"), value: discovered.length, hint: tx(t, "stats.discovered_hint", "known, not earned yet") }),
React.createElement(StatCard, { label: tx(t, "stats.secrets", "Secrets"), value: secret.length, hint: tx(t, "stats.secrets_hint", "hidden until first signal") }),
React.createElement(StatCard, { label: tx(t, "stats.highest_tier", "Highest tier"), value: highest, hint: tx(t, "stats.highest_tier_hint", "Copper → Silver → Gold → Diamond → Olympian") }),
- React.createElement(StatCard, { label: tx(t, "stats.latest", "Latest"), value: latest[0] ? latest[0].name : tx(t, "stats.none_yet", "None yet"), hint: latest[0] ? latest[0].category : tx(t, "stats.latest_hint_empty", "run Hermes more") })
+ React.createElement(StatCard, { label: tx(t, "stats.latest", "Latest"), value: latest[0] ? localizedAchievement(t, latest[0]).name : tx(t, "stats.none_yet", "None yet"), hint: latest[0] ? localizedAchievement(t, latest[0]).category : tx(t, "stats.latest_hint_empty", "run Hermes more") })
),
React.createElement("section", { className: "ha-guide" },
React.createElement("div", null,
@@ -694,7 +743,7 @@
React.createElement("div", { className: "ha-pills" }, categories.map(function (cat) {
// Render the localized "All" pill but keep the underlying value
// unchanged so the filter logic still compares against "All".
- const pillLabel = cat === "All" ? allCategoryLabel : cat;
+ const pillLabel = cat === "All" ? allCategoryLabel : localizedCategory(t, cat);
return React.createElement("button", { key: cat, onClick: function () { setCategory(cat); }, className: cat === category ? "active" : "" }, pillLabel);
})),
React.createElement("div", { className: "ha-pills" }, ["all", "unlocked", "discovered", "secret"].map(function (v) {
@@ -706,7 +755,7 @@
React.createElement("div", { className: "ha-latest-row" }, latest.map(function (a) {
return React.createElement("div", { key: a.id, className: cn("ha-chip", tierClass(a.tier)) },
React.createElement("span", { className: "ha-chip-icon" }, React.createElement(AchievementIcon, { icon: a.icon || "secret" })),
- a.name
+ localizedAchievement(t, a).name
);
}))
),
diff --git a/plugins/hermes-achievements/dashboard/plugin_api.py b/plugins/hermes-achievements/dashboard/plugin_api.py
index b419efc6c27f..32c8d42e438e 100644
--- a/plugins/hermes-achievements/dashboard/plugin_api.py
+++ b/plugins/hermes-achievements/dashboard/plugin_api.py
@@ -531,6 +531,30 @@ def metric_label(metric: str) -> str:
return METRIC_LABELS.get(metric, metric.replace("_", " "))
+def criteria_spec_for(definition: Dict[str, Any]) -> Dict[str, Any]:
+ """Return locale-neutral criteria data for dashboard clients.
+
+ ``criteria`` remains an English fallback for older plugin bundles, while
+ this structure lets a locale-aware client render the same requirement
+ without putting a locale into the scan snapshot cache.
+ """
+ if definition.get("secret") and definition.get("state") == "secret":
+ return {"kind": "secret_hidden"}
+ if "threshold_metric" in definition:
+ metric = definition["threshold_metric"]
+ return {
+ "kind": "tiered",
+ "metric": metric,
+ "metric_labels": {metric: metric_label(metric)},
+ "tiers": [dict(tier) for tier in definition.get("tiers", [])],
+ }
+ requirements = definition.get("requirements") or []
+ if requirements:
+ metrics = {requirement["metric"]: metric_label(requirement["metric"]) for requirement in requirements}
+ return {"kind": "requirements", "requirements": [dict(requirement) for requirement in requirements], "metric_labels": metrics}
+ return {"kind": "matching_workflow"}
+
+
def criteria_for(definition: Dict[str, Any]) -> str:
if definition.get("secret") and definition.get("state") == "secret":
return "Secret: exact requirement hidden until Hermes sees the first matching signal. Keep using Hermes across debugging, tools, memory, skills, plugins, and model workflows to reveal it."
@@ -551,9 +575,11 @@ def criteria_for(definition: Dict[str, Any]) -> str:
def display_achievement(item: Dict[str, Any]) -> Dict[str, Any]:
clean = dict(item)
+ criteria_spec = criteria_spec_for(clean)
if clean.get("state") == "secret":
- return {**clean, "name": "???", "description": "Secret achievement: hidden until Hermes detects the first relevant behavior in your session history.", "criteria": criteria_for(clean), "icon": "secret"}
+ return {**clean, "name": "???", "description": "Secret achievement: hidden until Hermes detects the first relevant behavior in your session history.", "criteria": criteria_for(clean), "criteria_spec": criteria_spec, "icon": "secret"}
clean["criteria"] = criteria_for(clean)
+ clean["criteria_spec"] = criteria_spec
return clean
diff --git a/plugins/hermes-achievements/tests/test_achievement_engine.py b/plugins/hermes-achievements/tests/test_achievement_engine.py
index 22342095269c..9edb2fd5e12d 100644
--- a/plugins/hermes-achievements/tests/test_achievement_engine.py
+++ b/plugins/hermes-achievements/tests/test_achievement_engine.py
@@ -73,6 +73,34 @@ def test_secret_achievement_stays_hidden_without_progress(self):
self.assertEqual(result["state"], "secret")
self.assertEqual(display["name"], "???")
self.assertNotIn("Permission", display["description"])
+ self.assertEqual(display["criteria_spec"], {"kind": "secret_hidden"})
+
+ def test_display_achievement_includes_locale_neutral_criteria_spec(self):
+ definition = {
+ "id": "terminal_goblin",
+ "threshold_metric": "total_terminal_calls",
+ "tiers": [{"name": "Copper", "threshold": 50}],
+ }
+
+ display = plugin_api.display_achievement({**definition, "state": "discovered"})
+
+ self.assertEqual(display["criteria_spec"], {
+ "kind": "tiered",
+ "metric": "total_terminal_calls",
+ "metric_labels": {"total_terminal_calls": "lifetime terminal calls"},
+ "tiers": [{"name": "Copper", "threshold": 50}],
+ })
+
+ def test_requirement_criteria_spec_contains_metric_label_fallbacks(self):
+ display = plugin_api.display_achievement({
+ "id": "full_send",
+ "state": "discovered",
+ "requirements": [{"metric": "total_terminal_calls", "gte": 10}],
+ })
+
+ self.assertEqual(display["criteria_spec"]["metric_labels"], {
+ "total_terminal_calls": "lifetime terminal calls",
+ })
def test_multi_condition_unlock_requires_all_requirements(self):
definition = {
diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts
index a8be9310835a..5d888f57159e 100644
--- a/web/src/i18n/en.ts
+++ b/web/src/i18n/en.ts
@@ -556,6 +556,147 @@ export const en: Translations = {
},
achievements: {
+ catalog: {
+ let_him_cook: { name: "Let Him Cook", description: "Let Hermes run a serious autonomous tool chain in one session." },
+ autonomous_avalanche: { name: "Autonomous Avalanche", description: "Accumulate a lifetime avalanche of Hermes tool calls across sessions." },
+ toolchain_maxxer: { name: "Toolchain Maxxer", description: "Use a wide spread of distinct Hermes tools in one session." },
+ full_send: { name: "Full Send", description: "Terminal, files, and web/browser all get involved in one real run." },
+ subagent_commander: { name: "Subagent Commander", description: "Coordinate delegated agent work." },
+ background_process_enjoyer: { name: "Background Process Enjoyer", description: "Start or control enough long-running processes to deserve the title." },
+ cron_necromancer: { name: "Cron Necromancer", description: "Raise scheduled autonomous jobs from the dead." },
+ red_text_connoisseur: { name: "Red Text Connoisseur", description: "Encounter enough errors to develop a palate for red text." },
+ stack_trace_sommelier: { name: "Stack Trace Sommelier", description: "Taste tracebacks by the flight, not by the sip." },
+ actually_read_the_logs: { name: "Actually Read The Logs", description: "Inspect logs repeatedly instead of guessing." },
+ port_3000_taken: { name: "Port 3000 Is Taken", description: "Discover dev-server port conflict patterns enough times to become numb." },
+ permission_denied_any_percent: { name: "Permission Denied Any%", description: "Speedrun into permission walls." },
+ dependency_hell_tourist: { name: "Dependency Hell Tourist", description: "Package installs fail, then somehow life continues." },
+ the_fix_was_restarting: { name: "The Fix Was Restarting It", description: "Restart after enough error clusters to call it a technique." },
+ forgot_the_env_var: { name: "Forgot The Env Var", description: "Auth or configuration failed because an environment variable was missing." },
+ yaml_colon_incident: { name: "YAML Colon Incident", description: "Configuration syntax bites back." },
+ docker_name_collision: { name: "Docker Name Collision", description: "A container name already exists. Of course it does." },
+ supposed_to_be_quick: { name: "This Was Supposed To Be Quick", description: "A tiny ask becomes an entire expedition." },
+ one_more_small_change: { name: "One More Small Change", description: "Make enough file edits in one session to invalidate the phrase small change." },
+ vibe_architect: { name: "Vibe Architect", description: "Touch a broad surface area in one project session." },
+ pixel_goblin: { name: "Pixel Goblin", description: "Do sustained frontend, CSS, SVG, or visual tuning." },
+ ship_first_ask_later: { name: "Ship First, Ask Later", description: "Git activity after a serious tool chain." },
+ css_exorcist: { name: "CSS Exorcist", description: "Cast repeated styling demons out of the interface." },
+ one_character_fix: { name: "One Character Fix", description: "A tiny edit after a pile of errors. Painful. Beautiful." },
+ skillsmith: { name: "Skillsmith", description: "Work with Hermes skills enough to leave fingerprints." },
+ skill_issue_skill_created: { name: "Skill Issue? Skill Created.", description: "Create or patch durable procedures instead of repeating yourself." },
+ memory_keeper: { name: "Memory Keeper", description: "Persist durable knowledge with memory or Mnemosyne." },
+ memory_palace: { name: "Memory Palace", description: "Build a serious durable-memory trail." },
+ context_dragon: { name: "Context Dragon", description: "Brush against compression, huge context, or token pressure repeatedly." },
+ gateway_dweller: { name: "Gateway Dweller", description: "Live through gateway-connected Hermes workflows." },
+ plugin_goblin: { name: "Plugin Goblin", description: "Use or develop plugins enough that the dashboard notices." },
+ rollback_wizard: { name: "Rollback Wizard", description: "Invoke rollback/checkpoint recovery magic." },
+ rabbit_hole_certified: { name: "Rabbit Hole Certified", description: "Search or extract enough web content to qualify as a research spiral." },
+ citation_goblin: { name: "Citation Goblin", description: "Extract enough web pages to become a tiny librarian." },
+ docs_archaeologist: { name: "Docs Archaeologist", description: "Dig through documentation sources over and over." },
+ browser_possession: { name: "Browser Possession", description: "Possess a browser through automation repeatedly." },
+ terminal_goblin: { name: "Terminal Goblin", description: "Spend serious time in shell-land." },
+ patch_wizard: { name: "Patch Wizard", description: "Bend files to your will with targeted patches." },
+ file_archaeologist: { name: "File Archaeologist", description: "Dig through the filesystem with reads and searches." },
+ image_whisperer: { name: "Image Whisperer", description: "Use image generation or vision tools enough for visual work." },
+ voice_of_the_machine: { name: "Voice Of The Machine", description: "Use text-to-speech or voice tooling repeatedly." },
+ model_hopper: { name: "Model Hopper", description: "Switch or inspect providers/models enough to count as a habit." },
+ openrouter_enjoyer: { name: "OpenRouter Enjoyer", description: "Route model work through OpenRouter repeatedly." },
+ codex_conjurer: { name: "Codex Conjurer", description: "Summon Codex-flavored assistance often enough for a ritual." },
+ multi_model_mage: { name: "Multi-Model Mage", description: "Use a real spread of distinct model names across Hermes history." },
+ five_model_flight: { name: "Five-Model Flight", description: "Try at least five distinct LLMs instead of marrying the first model that answers." },
+ provider_polyglot: { name: "Provider Polyglot", description: "Use models from multiple providers across Hermes history." },
+ model_sommelier: { name: "Model Sommelier", description: "Taste enough model/provider conversations to develop preferences." },
+ claude_confidant: { name: "Claude Confidant", description: "Bring Claude-flavored reasoning into the workflow repeatedly." },
+ gemini_cartographer: { name: "Gemini Cartographer", description: "Map enough Gemini-related workflows to know the terrain." },
+ open_weights_pilgrim: { name: "Open Weights Pilgrim", description: "Actually chat with local/open-weight models through Hermes session metadata." },
+ toolset_cartographer: { name: "Toolset Cartographer", description: "Navigate Hermes toolsets deliberately instead of treating tools as a blur." },
+ config_surgeon: { name: "Config Surgeon", description: "Operate on real config files, manifests, env files, and dashboard settings without flinching." },
+ rebase_acrobat: { name: "Rebase Acrobat", description: "Handle real git history surgery: rebase, conflict, merge, fetch, push." },
+ test_suite_tamer: { name: "Test Suite Tamer", description: "Run enough verification commands that green text becomes part of the ritual." },
+ screenshot_hunter: { name: "Screenshot Hunter", description: "Capture, inspect, and polish visual proof instead of just claiming it works." },
+ marathon_operator: { name: "Marathon Operator", description: "Accumulate a serious number of Hermes sessions." },
+ weekend_warrior: { name: "Weekend Warrior", description: "Run Hermes on weekends enough times to make it a lifestyle." },
+ night_shift_operator: { name: "Night Shift Operator", description: "Run sessions during gremlin hours repeatedly." },
+ cache_hit_appreciator: { name: "Cache Hit Appreciator", description: "Notice or benefit from prompt/cache behavior." },
+ },
+ metrics: {
+ max_tool_calls_in_session: "tool calls in one session",
+ max_distinct_tools_in_session: "distinct Hermes tools used in one session",
+ max_terminal_calls_in_session: "terminal calls in one session",
+ max_file_tool_calls_in_session: "file/search/patch calls in one session",
+ max_web_browser_calls_in_session: "web search/extract or browser calls in one session",
+ max_messages_in_session: "messages in one session",
+ max_files_touched_in_session: "files touched in one session",
+ total_delegate_calls: "lifetime delegate_task calls",
+ total_process_calls: "lifetime background process operations",
+ total_cron_calls: "lifetime scheduled-job operations",
+ total_errors: "error/failed/traceback messages observed",
+ traceback_events: "traceback or exception mentions",
+ log_read_events: "log inspections",
+ port_conflict_events: "dev-server port conflict detections",
+ permission_denied_events: "permission-denied errors",
+ install_error_events: "package-install failures",
+ install_success_events: "successful package installs after package work",
+ restart_after_error_events: "restart/reload actions after error clusters",
+ env_var_error_events: "missing auth/config/environment-variable events",
+ yaml_error_events: "YAML/config parse incidents",
+ docker_conflict_events: "Docker/container-name conflicts",
+ frontend_activity_events: "frontend/CSS/SVG/React activity mentions",
+ css_activity_events: "CSS, styling, Tailwind, or className activity",
+ git_events: "git workflow commands",
+ tiny_patch_after_errors_events: "tiny typo-style fixes after error clusters",
+ skill_events: "Hermes skill mentions or tool use",
+ skill_manage_events: "skill_manage create/patch/delete operations",
+ memory_events: "memory or Mnemosyne tool events",
+ memory_write_events: "durable memory writes",
+ context_events: "context, compression, token, or cache-pressure mentions",
+ gateway_events: "gateway/API/chat-platform activity",
+ plugin_events: "dashboard plugin development or usage signals",
+ rollback_events: "rollback/checkpoint recovery mentions",
+ docs_activity_events: "documentation/README/docs activity",
+ model_events: "model/provider-related activity",
+ openrouter_events: "OpenRouter mentions",
+ codex_events: "Codex mentions",
+ cache_events: "prompt-cache/cache-hit mentions",
+ total_web_calls: "lifetime web_search/web_extract calls",
+ total_web_extract_calls: "lifetime web_extract calls",
+ browser_calls: "lifetime browser automation calls",
+ total_tool_calls: "lifetime Hermes tool calls",
+ total_terminal_calls: "lifetime terminal calls",
+ total_patch_calls: "lifetime targeted patch edits",
+ total_file_reads_searches: "lifetime read_file/search_files calls",
+ image_vision_calls: "image generation or vision tool calls",
+ tts_calls: "text-to-speech or voice tool calls",
+ distinct_model_count: "distinct model names seen in session metadata",
+ distinct_provider_count: "distinct model providers inferred from session metadata",
+ claude_events: "Claude/Anthropic model mentions",
+ gemini_events: "Gemini/Google model mentions",
+ local_model_events: "local/open-weight model mentions",
+ local_model_chat_sessions: "Hermes sessions whose model metadata is local/open-weight",
+ toolset_events: "toolset or tool-family mentions",
+ config_events: "configuration/environment/manifest activity",
+ git_history_events: "git history operations such as rebase, merge, fetch, push, or tag",
+ test_events: "test/check/verification command mentions",
+ screenshot_events: "screenshot, Playwright, PNG, or vision-inspection activity",
+ release_events: "release, version, publish, or git tag events",
+ session_count: "Hermes sessions",
+ weekend_sessions: "sessions started on weekends",
+ night_sessions: "sessions started late night or before dawn",
+ },
+ criteria: {
+ tiered: "Requirement: {metric}. Tier ladder: {ladder}.",
+ requirements: "Requirement: {requirements}.",
+ secret_hidden: "Secret: exact requirement hidden until Hermes sees the first matching signal. Keep using Hermes across debugging, tools, memory, skills, plugins, and model workflows to reveal it.",
+ },
+ categories: {
+ "Agent Autonomy": "Agent Autonomy",
+ "Debugging Chaos": "Debugging Chaos",
+ "Vibe Coding": "Vibe Coding",
+ "Hermes Native": "Hermes Native",
+ "Research/Web": "Research/Web",
+ "Tool Mastery": "Tool Mastery",
+ "Model Lore": "Model Lore",
+ "Lifestyle": "Lifestyle",
+ },
hero: {
kicker: "Agentic Gamerscore",
title: "Hermes Achievements",
diff --git a/web/src/i18n/types.ts b/web/src/i18n/types.ts
index 6a888226adbf..28a08caf01fa 100644
--- a/web/src/i18n/types.ts
+++ b/web/src/i18n/types.ts
@@ -578,6 +578,21 @@ export interface Translations {
// ── Achievements plugin (plugins/hermes-achievements) ──
achievements: {
+ /** Optional achievement copy keyed by the stable ACHIEVEMENTS id. */
+ catalog?: Record;
+ /** Optional labels keyed by METRIC_LABELS keys. */
+ metrics?: Record;
+ /** Optional templates for locale-aware achievement criteria. */
+ criteria?: {
+ tiered?: string;
+ requirements?: string;
+ secret_hidden?: string;
+ };
+ /** Optional labels keyed by the canonical achievement category. */
+ categories?: Record;
hero: {
kicker: string;
title: string;