{"record":{"id":"19fe51341ebe8a80","repo":"Humanizr/Humanizer","slug":"phrase-path-must-define-single-multiple","errorCode":null,"errorMessage":"Phrase '{path}' must define 'single', 'multiple', or 'template'.","messagePattern":"Phrase '(.+?)' must define 'single', 'multiple', or 'template'\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Humanizer.SourceGenerators/Common/LocalePhraseNormalization.cs","lineNumber":107,"sourceCode":"        {\n            if (value is SimpleYamlScalar scalar)\n            {\n                return new DateHumanizePhrase(ValidateLiteralText(path, scalar.Value));\n            }\n\n            var mapping = ExpectMapping(value, path);\n            RejectUnknownKeys(mapping, path, [\"single\", \"multiple\", \"template\"]);\n\n            var single = GetOptionalLiteral(mapping, \"single\", $\"{path}.single\");\n            var multiple = mapping.TryGetValue(\"multiple\", out var multipleValue)\n                ? ParseCountedPhrase(multipleValue, $\"{path}.multiple\")\n                : null;\n            var template = mapping.TryGetValue(\"template\", out var templateValue)\n                ? ParseNamedTemplate(templateValue, $\"{path}.template\", [])\n                : null;\n            if (single is null && multiple is null && template is null)\n            {\n                throw new InvalidOperationException($\"Phrase '{path}' must define 'single', 'multiple', or 'template'.\");\n            }\n\n            return new DateHumanizePhrase(single, multiple, template);\n        }\n\n        static TimeSpanPhraseSet ParseTimeSpan(\n            string localeCode,\n            SimpleYamlValue value,\n            string path,\n            bool preserveDurationCaseForms)\n        {\n            var mapping = ExpectMapping(value, path);\n            var builder = ImmutableDictionary.CreateBuilder<string, TimeSpanPhrase>(StringComparer.Ordinal);\n\n            foreach (var entry in mapping.Values)\n            {\n                if (entry.Key is \"zero\" or \"age\")\n                {","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/Humanizr/Humanizer/blob/ffc2b77c0f30d2fb176875841424379319d0ae9b/src/Humanizer.SourceGenerators/Common/LocalePhraseNormalization.cs#L89-L125","documentation":"Thrown by LocalePhraseNormalization.ParseDateHumanizePhrase when a relative-date phrase entry is a mapping but contains none of the allowed keys ('single', 'multiple', 'template'). An empty mapping (or one with only unknown keys, which are rejected earlier) is invalid because the phrase must carry at least one form to render.","triggerScenarios":"A locale YAML defines a relativeDate phrase unit (e.g. past.day) as a mapping that omits single, multiple, and template. After RejectUnknownKeys filters stray keys, all three optionals resolve to null and the check fires.","commonSituations":"A contributor adds a phrase entry with a wrong key name (e.g. 'one' instead of 'single'), or an empty mapping placeholder, then runs the build. The error names the phrase path (e.g. en.phrases.relativeDate.past.day).","solutions":["In the named phrase path, add at least one of 'single', 'multiple', or 'template'.","Check for typos in key names; unknown keys are rejected before this check, so the path message pinpoints the location.","Rebuild to confirm the generator accepts the phrase."],"exampleFix":"# before\nrelativeDate:\n  past:\n    day:\n      {}\n# after\nrelativeDate:\n  past:\n    day:\n      single: 'yesterday'","handlingStrategy":"validation","validationCode":"# For each relativeDate phrase mapping, confirm at least one of the allowed\n# keys is present:\n#   rg -A3 \"relativeDate\" src/Humanizer/Locales/<code>.yml\n# Every non-scalar phrase entry needs single, multiple, or template.","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Phrase mappings must contain at least one of single/multiple/template; empty mappings are invalid.","Check key spelling; unknown keys are rejected before the emptiness check.","Build locally after phrase edits to catch missing forms."],"tags":["source-generator","locale-yaml","configuration","build-time","phrases","humanizer"],"backgroundTag":null,"analyzedSha":"ffc2b77c0f30d2fb176875841424379319d0ae9b","analyzedAt":"2026-08-13T21:42:34.584Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}