{"record":{"id":"2a71836128b43d99","repo":"File-New-Project/EarTrumpet","slug":"missing-resource-res","errorCode":null,"errorMessage":"Missing resource: {res}","messagePattern":"Missing resource: (.+?)","errorType":"exception","errorClass":"NotImplementedException","httpStatus":null,"severity":"error","filePath":"EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/HotkeyViewModel.cs","lineNumber":40,"sourceCode":"        public override string ToString()\r\n        {\r\n            if (_trigger.Option.IsEmpty)\r\n            {\r\n                return ResolveResource(\"EmptyText\");\r\n            }\r\n            else\r\n            {\r\n                return _trigger.Option.ToString();\r\n            }\r\n        }\r\n\r\n        private string ResolveResource(string suffix)\r\n        {\r\n            var res = $\"{_trigger.GetType().Name}_{suffix}\";\r\n            var ret = Properties.Resources.ResourceManager.GetString(res);\r\n            if (string.IsNullOrWhiteSpace(ret))\r\n            {\r\n                throw new NotImplementedException($\"Missing resource: {res}\");\r\n            }\r\n            return ret;\r\n        }\r\n    }\r\n}\r\n","sourceCodeStart":22,"sourceCodeEnd":46,"githubUrl":"https://github.com/File-New-Project/EarTrumpet/blob/aa894e51c22f5f9a939b31b224c4d2d3e163416e/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/HotkeyViewModel.cs#L22-L46","documentation":"HotkeyViewModel.ResolveResource builds a resource key \"{TypeName}_{suffix}\" from the runtime type name of the wrapped HotkeyTrigger and looks it up in the EarTrumpet.Actions Properties.Resources (.resx). The only suffix used here is \"EmptyText\", requested from ToString() when the trigger's hotkey Option is empty. If ResourceManager.GetString returns null or whitespace for the key \"HotkeyTrigger_EmptyText\", the method treats the resource contract as broken and throws NotImplementedException. This is a localization/content invariant: every Trigger type name must have a matching resx entry.","triggerScenarios":"The UI calls HotkeyViewModel.ToString() (e.g. while rendering the Actions editor list) while _trigger.Option.IsEmpty is true, so ResolveResource(\"EmptyText\") runs. The key \"HotkeyTrigger_EmptyText\" is absent from the EarTrumpet.Actions resx (neutral or current culture), so GetString returns null and the throw fires.","commonSituations":"A developer renamed the HotkeyTrigger class but did not update the resx key prefix; a localized satellite resx omits the key; the .resx code generator did not run after a resx edit; a new trigger variant was introduced without its display strings.","solutions":["Open EarTrumpet/Addons/EarTrumpet.Actions/Properties/Resources.resx (and any .<culture>.resx) and add a data entry named \"HotkeyTrigger_EmptyText\" with the placeholder text (e.g. \"Select a hotkey\").","If you renamed the HotkeyTrigger type, either restore the old name or add resx entries whose prefix matches the new type name exactly (the code uses GetType().Name).","Rebuild the project so Resources.Designer.cs / ResourceManager regenerates and the new key is embedded.","Verify with a quick grep that every GetType().Name produced by this code path has all used suffixes (EmptyText) present in the resx."],"exampleFix":"// before (resx missing the key)\n//   <data name=\"HotkeyTrigger_EmptyText\"> ... absent ...\n\n// after: add to Resources.resx\n//   <data name=\"HotkeyTrigger_EmptyText\" xml:space=\"preserve\">\n//     <value>Select a hotkey</value>\n//   </data>","handlingStrategy":"validation","validationCode":"// Run before relying on the resource\nvar key = $\"{trigger.GetType().Name}_EmptyText\";\nvar value = Properties.Resources.ResourceManager.GetString(key);\nif (string.IsNullOrWhiteSpace(value))\n{\n    // missing resource: log and supply a safe default instead of throwing\n    Trace.WriteLine($\"Missing resource: {key}\");\n    return string.Empty;\n}","typeGuard":null,"tryCatchPattern":"try { return ResolveResource(\"EmptyText\"); } catch (NotImplementedException ex) { Trace.WriteLine(ex); return string.Empty; }","preventionTips":["Treat the resx key prefix as part of the type's public contract: renaming a Trigger requires renaming its resx keys.","Add a unit test that reflects over all Trigger types and asserts \"{TypeName}_EmptyText\" resolves.","Run a resx-key audit in CI: every GetType().Name + suffix used by ResolveResource must exist."],"tags":["resource","localization","eartrumpet-actions","resx","viewmodel"],"backgroundTag":null,"analyzedSha":"aa894e51c22f5f9a939b31b224c4d2d3e163416e","analyzedAt":"2026-08-13T18:51:30.564Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}