{"record":{"id":"c1bd104868d23f2e","repo":"Humanizr/Humanizer","slug":"phrase-path-can-only-use-placeholders-allowe","errorCode":null,"errorMessage":"Phrase '{path}' can only use placeholders: {allowedPlaceholders}.","messagePattern":"Phrase '(.+?)' can only use placeholders: (.+?)\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Humanizer.SourceGenerators/Common/LocalePhraseNormalization.cs","lineNumber":530,"sourceCode":"        {\n            foreach (var placeholder in GetPlaceholderNames(text))\n            {\n                if (int.TryParse(placeholder, out _))\n                {\n                    throw new InvalidOperationException($\"Phrase '{path}' cannot use numeric placeholder '{{{placeholder}}}'.\");\n                }\n\n                if (placeholder.Length == 0 || !char.IsLetter(placeholder[0]) || placeholder.Any(static c => !char.IsLetterOrDigit(c) && c != '_'))\n                {\n                    throw new InvalidOperationException($\"Phrase '{path}' uses unsupported placeholder '{{{placeholder}}}'.\");\n                }\n\n                if (!allowedPlaceholders.Contains(placeholder, StringComparer.Ordinal))\n                {\n                    throw new InvalidOperationException(\n                        allowedPlaceholders.Length == 0\n                            ? $\"Phrase '{path}' can only use placeholders in explicit 'template' entries.\"\n                            : $\"Phrase '{path}' can only use placeholders: {string.Join(\", \", allowedPlaceholders.Select(static value => $\"{{{value}}}\"))}.\");\n                }\n            }\n\n            return text;\n        }\n\n        static IEnumerable<string> GetPlaceholderNames(string text) =>\n            PlaceholderRegex.Matches(text)\n                .Cast<Match>()\n                .Select(static match => match.Groups[\"name\"].Value);\n\n        static SimpleYamlMapping ExpectMapping(SimpleYamlValue value, string path) =>\n            value as SimpleYamlMapping ??\n            throw new InvalidOperationException($\"Phrase section '{path}' must be a mapping.\");\n\n        static void RejectUnknownKeys(SimpleYamlMapping mapping, string path, params string[] allowedKeys)\n        {\n            foreach (var key in mapping.Values.Keys.Where(key => !allowedKeys.Contains(key, StringComparer.Ordinal)))","sourceCodeStart":512,"sourceCodeEnd":548,"githubUrl":"https://github.com/Humanizr/Humanizer/blob/ffc2b77c0f30d2fb176875841424379319d0ae9b/src/Humanizer.SourceGenerators/Common/LocalePhraseNormalization.cs#L512-L548","documentation":"Thrown by ValidateLiteralText when a literal phrase string uses a placeholder that is syntactically valid but not in the allowed set for that field (allowedPlaceholders is non-empty but does not contain the name). For example, counted-phrase form literals allow {count} (and {prep} when countPlacement is none) but not {unit}. Surfaced as compiler diagnostic HSG003 (severity Error).","triggerScenarios":"Writing 'default: {unit} hours' in a counted-phrase form literal (only count/prep allowed), or 'beforeCount: {count} {unit}' (only prep allowed there).","commonSituations":"Author reuses a placeholder valid on templates inside a literal field with a narrower allowance; misunderstanding which fields accept which names.","solutions":["Read the diagnostic: it lists the placeholders allowed at that path.","Replace the disallowed placeholder with an allowed one, or remove it.","Move complex interpolation into an explicit 'template' entry for that phrase.","Rebuild to confirm the diagnostic clears."],"exampleFix":"# before (counted phrase form, only {count} allowed)\nmultiple:\n  forms:\n    default: '{unit} of {count}'\n# after\nmultiple:\n  forms:\n    default: '{count}'","handlingStrategy":"validation","validationCode":"# Counted-phrase form literals allow {count} (and {prep} when countPlacement is none).\n# Flag any other placeholder inside default/singular/plural/etc.\ngrep -nE '(default|zero|singular|dual|paucal|plural|many):.*\\{(?!count|prep$)[A-Za-z_][A-Za-z0-9_]*\\}' src/Humanizer/Locales/<code>.yml","typeGuard":null,"tryCatchPattern":null,"preventionTips":["In counted-phrase forms use only {count} (or {prep} when countPlacement is none).","For richer interpolation add an explicit 'template' entry.","Build after editing forms."],"tags":["humanizer","source-generator","locale","yaml","localization","phrases","placeholder","literal"],"backgroundTag":null,"analyzedSha":"ffc2b77c0f30d2fb176875841424379319d0ae9b","analyzedAt":"2026-08-13T21:42:34.584Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}