{"record":{"id":"255ba54cc131c998","repo":"Humanizr/Humanizer","slug":"canonical-list-templates-must-use-0-and-1","errorCode":null,"errorMessage":"Canonical list templates must use '{0}' and '{1}' as an infix template.","messagePattern":"Canonical list templates must use '(.+?)' and '(.+?)' as an infix template\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Humanizer.SourceGenerators/Common/CanonicalLocaleAuthoring.cs","lineNumber":629,"sourceCode":"                values[\"value\"] = new SimpleYamlScalar(ExtractTemplateSeparator(finalTemplate), isQuoted: true);\n            }\n\n            return new SimpleYamlMapping(values.ToImmutable());\n        }\n\n        static bool ReadBoolean(SimpleYamlMapping mapping, string key) =>\n            mapping.TryGetValue(key, out var value) &&\n            value is SimpleYamlScalar scalar &&\n            bool.TryParse(scalar.Value, out var result) &&\n            result;\n\n        static string ExtractTemplateSeparator(string template)\n        {\n            const string start = \"{0}\";\n            const string end = \"{1}\";\n            return !template.StartsWith(start, StringComparison.Ordinal) ||\n                !template.EndsWith(end, StringComparison.Ordinal)\n                ? throw new InvalidOperationException(\"Canonical list templates must use '{0}' and '{1}' as an infix template.\")\n                : template.Substring(start.Length, template.Length - start.Length - end.Length).Trim();\n        }\n    }\n\n    public static class LegacyLocaleMigration\n    {\n        static readonly string[] LegacyTopLevelNames =\n        [\n            \"inherits\",\n            \"collectionFormatter\",\n            \"dateOnlyToOrdinalWords\",\n            \"dateToOrdinalWords\",\n            \"durationCases\",\n            \"formatter\",\n            \"grammar\",\n            \"headings\",\n            \"numberToWords\",\n            \"ordinalizer\",","sourceCodeStart":611,"sourceCodeEnd":647,"githubUrl":"https://github.com/Humanizr/Humanizer/blob/ffc2b77c0f30d2fb176875841424379319d0ae9b/src/Humanizer.SourceGenerators/Common/CanonicalLocaleAuthoring.cs#L611-L647","documentation":"Thrown by ExtractTemplateSeparator when a canonical list template (pairTemplate, finalTemplate, or serialTemplate) does not start with '{0}' and end with '{1}'. The generator extracts the infix separator between these two placeholders to produce the legacy 'value' string. Templates that don't follow this exact infix shape cannot be normalized.","triggerScenarios":"NormalizeListSurface calls ExtractTemplateSeparator on finalTemplate (which defaults to pairTemplate if finalTemplate is absent). If the template string lacks the {0}...{1} wrapper — e.g. 'pairTemplate: \", \"' or 'pairTemplate: \"{0}{1}\"' — StartsWith/EndsWith fails and the throw expression fires.","commonSituations":"A contributor writes a plain separator string (e.g. ', ') instead of a full template, or puts the separator outside the placeholders. Also occurs when copy-pasting a template from documentation that omits the placeholders.","solutions":["Rewrite the template as an infix: start with '{0}', end with '{1}', put the separator between them, e.g. 'pairTemplate: \"{0}, {1}\"'.","If you only want a bare separator with no special final/serial behavior, use the legacy 'value' scalar instead of templates.","Verify that finalTemplate and serialTemplate (if present) also follow the same {0}...{1} shape."],"exampleFix":"# before\nsurfaces:\n  list:\n    engine: conjunction\n    pairTemplate: \", \"\n# after\nsurfaces:\n  list:\n    engine: conjunction\n    pairTemplate: \"{0}, {1}\"","handlingStrategy":"validation","validationCode":"// Ensure list templates use {0}...{1} infix shape\nstatic bool TemplateIsInfix(string template) =>\n    template.StartsWith(\"{0}\", StringComparison.Ordinal) &&\n    template.EndsWith(\"{1}\", StringComparison.Ordinal) &&\n    template.Length >= \"{0}{1}\".Length;","typeGuard":null,"tryCatchPattern":null,"preventionTips":["All list templates must start with {0} and end with {1} with the separator between them.","Use 'value' (bare separator) instead of templates for simple delimited output.","Test template strings with a quick regex: ^\\\\{0\\}.*\\\\{1\\}$"],"tags":["source-generator","locale","yaml","list-formatting","template"],"backgroundTag":null,"analyzedSha":"ffc2b77c0f30d2fb176875841424379319d0ae9b","analyzedAt":"2026-08-13T21:42:34.584Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}