{"record":{"id":"b91ec6e7c784e414","repo":"Humanizr/Humanizer","slug":"phrase-path-must-define-numeric-text-or","errorCode":null,"errorMessage":"Phrase '{path}' must define 'numeric', 'text', or 'words'.","messagePattern":"Phrase '(.+?)' must define 'numeric', 'text', or 'words'\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Humanizer.SourceGenerators/Common/LocalePhraseNormalization.cs","lineNumber":333,"sourceCode":"                namedTemplate);\n        }\n\n        static (string? Single, string? WordsVariant) ParseSinglePhraseWithWordsVariant(SimpleYamlValue value, string path)\n        {\n            if (value is SimpleYamlScalar scalar)\n            {\n                return (ValidateLiteralText(path, scalar.Value), null);\n            }\n\n            var mapping = ExpectMapping(value, path);\n            RejectUnknownKeys(mapping, path, [\"numeric\", \"text\", \"words\"]);\n\n            var single = GetOptionalLiteral(mapping, \"numeric\", $\"{path}.numeric\") ??\n                GetOptionalLiteral(mapping, \"text\", $\"{path}.text\");\n            var wordsVariant = GetOptionalLiteral(mapping, \"words\", $\"{path}.words\");\n            if (single is null && wordsVariant is null)\n            {\n                throw new InvalidOperationException($\"Phrase '{path}' must define 'numeric', 'text', or 'words'.\");\n            }\n\n            return (single, wordsVariant);\n        }\n\n        static PhraseForms? ParseOptionalPhraseForms(\n            SimpleYamlMapping mapping,\n            string path,\n            params string[] allowedPlaceholders) =>\n            ParseOptionalPhraseForms(mapping, path, preserveDuplicateForms: false, allowedPlaceholders);\n\n        static PhraseForms? ParseOptionalPhraseForms(\n            SimpleYamlMapping mapping,\n            string path,\n            bool preserveDuplicateForms,\n            params string[] allowedPlaceholders)\n        {\n            if (mapping.TryGetValue(\"forms\", out var formsValue))","sourceCodeStart":315,"sourceCodeEnd":351,"githubUrl":"https://github.com/Humanizr/Humanizer/blob/ffc2b77c0f30d2fb176875841424379319d0ae9b/src/Humanizer.SourceGenerators/Common/LocalePhraseNormalization.cs#L315-L351","documentation":"Thrown by ParseSinglePhraseWithWordsVariant when a duration phrase's 'single' entry is a mapping but defines none of 'numeric', 'text', or 'words'. The single form must carry at least one of these so the generator can render the count-1 case. Surfaced as compiler diagnostic HSG003 (severity Error).","triggerScenarios":"Authoring phrases.duration.<unit>.single as a mapping with only unknown keys, or with all three of numeric/text/words set to unquoted null.","commonSituations":"Author writes a single: {} placeholder intending to come back to it; copies a mapping from another unit and removes the value lines; sets 'numeric:' with no value.","solutions":["Provide 'numeric' (digit form, e.g. '1 hour'), 'text' (spelled-out form), and/or 'words' (words-only variant) under the single mapping.","If no distinction is needed, replace the mapping with a scalar string for single.","Remove the single key to inherit or omit it as the locale design allows.","Rebuild to confirm the diagnostic clears."],"exampleFix":"# before\nduration:\n  hour:\n    single: {}\n# after\nduration:\n  hour:\n    single:\n      numeric: 1 hour\n      words: one hour","handlingStrategy":"validation","validationCode":"# A 'single' mapping under a duration unit must define numeric/text/words.\n#   yq -o=json '.phrases.duration | to_entries[] | select(.value.single | tag == \"!!map\") | select((.value.single.numeric // .value.single.text // .value.single.words) == null) | .key' src/Humanizer/Locales/<code>.yml","typeGuard":null,"tryCatchPattern":null,"preventionTips":["If 'single' needs no variants, make it a scalar rather than a mapping.","When using a mapping, fill numeric (or text) immediately.","Build after each edit to catch the empty mapping early."],"tags":["humanizer","source-generator","locale","yaml","localization","phrases","duration","single"],"backgroundTag":null,"analyzedSha":"ffc2b77c0f30d2fb176875841424379319d0ae9b","analyzedAt":"2026-08-13T21:42:34.584Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}