{"record":{"id":"647f209f5da82245","repo":"File-New-Project/EarTrumpet","slug":"missing-resource-res-647f20","errorCode":null,"errorMessage":"Missing resource: {res}","messagePattern":"Missing resource: (.+?)","errorType":"exception","errorClass":"NotImplementedException","httpStatus":null,"severity":"error","filePath":"EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/PartViewModel.cs","lineNumber":75,"sourceCode":"            Part = part;\r\n        }\r\n\r\n        protected void Attach(INotifyPropertyChanged obj)\r\n        {\r\n            obj.PropertyChanged += (s, e) =>\r\n            {\r\n                RaisePropertyChanged(e.PropertyName);\r\n                RaisePropertyChanged(nameof(LinkText));\r\n            };\r\n        }\r\n\r\n        private string ResolveResource(string suffix)\r\n        {\r\n            var res = $\"{Part.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}","sourceCodeStart":57,"sourceCodeEnd":80,"githubUrl":"https://github.com/File-New-Project/EarTrumpet/blob/aa894e51c22f5f9a939b31b224c4d2d3e163416e/EarTrumpet/Addons/EarTrumpet.Actions/ViewModel/PartViewModel.cs#L57-L80","documentation":"PartViewModel is the base class for every trigger/condition/action view model in the EarTrumpet.Actions addon. Its AddText and LinkText properties both call ResolveResource(suffix), forming the key \"{Part.GetType().Name}_{suffix}\" against the Actions resx. Because every concrete Part-derived data type flows through this base, each one must ship both \"{TypeName}_AddText\" and \"{TypeName}_LinkText\" resource strings. A null/whitespace lookup throws NotImplementedException, which is a hard failure during UI binding.","triggerScenarios":"Any XAML binding or code access to AddText / LinkText on a PartViewModel whose wrapped Part runtime type lacks one of the resx keys. E.g. a new action type 'SetDefaultDeviceAction' is rendered before \"{SetDefaultDeviceAction}_LinkText\" exists.","commonSituations":"Adding a new Part subclass (new action/condition/trigger) without adding its two resx entries; renaming a Part type without mirroring the resx; partial localized resx missing the keys for some parts.","solutions":["For the offending Part type name (shown in the thrown message), add both \"{TypeName}_AddText\" and \"{TypeName}_LinkText\" to Resources.resx and each localized resx.","Audit all Part-derived types (search for ': Part' / ': BaseTrigger' / ': BaseCondition' / ': BaseAction') and ensure each has AddText + LinkText resx entries.","If you renamed the type, update the resx key prefix to match the new GetType().Name.","Add a unit test that reflects over all Part subtypes and asserts both keys resolve non-empty."],"exampleFix":"// before: new action type has no resx strings\n//   public class SetDefaultDeviceAction : BaseAction { }\n\n// after: add resx entries\n//   <data name=\"SetDefaultDeviceAction_AddText\"><value>Set as default</value></data>\n//   <data name=\"SetDefaultDeviceAction_LinkText\"><value>device</value></data>","handlingStrategy":"validation","validationCode":"var key = $\"{Part.GetType().Name}_{suffix}\";\nvar value = Properties.Resources.ResourceManager.GetString(key);\nif (string.IsNullOrWhiteSpace(value)) { Trace.WriteLine($\"Missing resource: {key}\"); return string.Empty; }","typeGuard":null,"tryCatchPattern":"try { return ResolveResource(suffix); } catch (NotImplementedException ex) { Trace.WriteLine(ex); return string.Empty; }","preventionTips":["When adding a Part subtype, add AddText + LinkText resx entries in the same commit.","CI: reflect over all Part-derived types and assert both keys exist in the resx.","Never rename a Part type without a matching resx key rename."],"tags":["resource","localization","eartrumpet-actions","resx","viewmodel","part"],"backgroundTag":null,"analyzedSha":"aa894e51c22f5f9a939b31b224c4d2d3e163416e","analyzedAt":"2026-08-13T18:51:30.564Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}