{"record":{"id":"3728789eeecf0b12","repo":"Humanizr/Humanizer","slug":"locale-localecode-surfaces-list-must-define-ca","errorCode":null,"errorMessage":"Locale '{localeCode}.surfaces.list' must define canonical list templates or legacy 'value'.","messagePattern":"Locale '(.+?)\\.surfaces\\.list' must define canonical list templates or legacy 'value'\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Humanizer.SourceGenerators/Common/CanonicalLocaleAuthoring.cs","lineNumber":590,"sourceCode":"        {\n            if (mapping.TryGetValue(\"value\", out _))\n            {\n                return mapping;\n            }\n\n            var engine = mapping.GetScalar(\"engine\")\n                ?? throw new InvalidOperationException($\"Locale '{localeCode}.surfaces.list' must define 'engine'.\");\n            var pairTemplate = mapping.GetScalar(\"pairTemplate\");\n            var finalTemplate = mapping.GetScalar(\"finalTemplate\");\n            var serialTemplate = mapping.GetScalar(\"serialTemplate\");\n            if (engine == \"oxford\" && pairTemplate is null && finalTemplate is null)\n            {\n                return mapping;\n            }\n\n            if (pairTemplate is null)\n            {\n                throw new InvalidOperationException(\n                    $\"Locale '{localeCode}.surfaces.list' must define canonical list templates or legacy 'value'.\");\n            }\n\n            finalTemplate ??= pairTemplate;\n\n            var values = ImmutableDictionary.CreateBuilder<string, SimpleYamlValue>(StringComparer.Ordinal);\n            var oxfordComma = ReadBoolean(mapping, \"oxfordComma\");\n            var cliticizesFinal = ReadBoolean(mapping, \"cliticizesFinal\");\n            values[\"engine\"] = new SimpleYamlScalar(\n                oxfordComma || engine == \"oxford\"\n                    ? \"oxford\"\n                    : cliticizesFinal\n                        ? \"clitic\"\n                        : engine == \"delimited\" || pairTemplate == finalTemplate && pairTemplate == serialTemplate\n                            ? \"delimited\"\n                            : \"conjunction\",\n                isQuoted: true);\n","sourceCodeStart":572,"sourceCodeEnd":608,"githubUrl":"https://github.com/Humanizr/Humanizer/blob/ffc2b77c0f30d2fb176875841424379319d0ae9b/src/Humanizer.SourceGenerators/Common/CanonicalLocaleAuthoring.cs#L572-L608","documentation":"Thrown by NormalizeListSurface when surfaces.list declares an engine and has no legacy 'value', but is missing the required 'pairTemplate'. The canonical list engine needs at least pairTemplate to build the join logic. The only exception is engine: 'oxford' with no templates at all (which uses built-in defaults). Any other engine without pairTemplate fails.","triggerScenarios":"NormalizeListSurface checks: if value is absent and engine is present and not (oxford with no templates), then pairTemplate must exist. For example 'surfaces:\\n  list:\\n    engine: conjunction' with no pairTemplate triggers it.","commonSituations":"A contributor sets engine to 'conjunction' or 'delimited' but only provides finalTemplate or serialTemplate, forgetting pairTemplate. Or they partially fill in templates after copying an oxford block (which doesn't require them).","solutions":["Add a 'pairTemplate' scalar using {0} and {1} placeholders, e.g. 'pairTemplate: \"{0}, {1}\"'.","If you intended the oxford default templates, set 'engine: oxford' and remove all template keys so the built-in oxford defaults apply.","Switch to the legacy 'value' form (a bare separator scalar) if you only need simple delimited output."],"exampleFix":"# before\nsurfaces:\n  list:\n    engine: conjunction\n# after\nsurfaces:\n  list:\n    engine: conjunction\n    pairTemplate: \"{0}, {1}\"","handlingStrategy":"validation","validationCode":"// Verify pairTemplate is present when engine is not bare-oxford\nstatic bool ListTemplatesAreComplete(SimpleYamlMapping listMap)\n{\n    if (listMap.TryGetValue(\"value\", out _)) return true;\n    var engine = listMap.GetScalar(\"engine\");\n    if (engine == \"oxford\" && listMap.GetScalar(\"pairTemplate\") is null && listMap.GetScalar(\"finalTemplate\") is null)\n        return true;\n    return listMap.GetScalar(\"pairTemplate\") is not null;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always provide pairTemplate when using conjunction/delimited/clitic engines.","Remember that oxford with no templates is the only engine that doesn't require pairTemplate.","Validate templates in a local test before pushing."],"tags":["source-generator","locale","yaml","list-formatting","collection-formatter"],"backgroundTag":null,"analyzedSha":"ffc2b77c0f30d2fb176875841424379319d0ae9b","analyzedAt":"2026-08-13T21:42:34.584Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}