{"id":"116be4c7f928532d","repo":"gohugoio/hugo","slug":"uneven-number-of-replacement-pairs","errorCode":null,"errorMessage":"uneven number of replacement pairs","messagePattern":"uneven number of replacement pairs","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tpl/strings/strings.go","lineNumber":301,"sourceCode":"\t}\n\tif p == nil {\n\t\t// inline form: ReplacePairs \"a\" \"b\" \"s\"\n\t\tp = make([]string, len(args)-1)\n\t\tfor i, v := range args[:len(args)-1] {\n\t\t\ts, err := cast.ToStringE(v)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\tp[i] = s\n\t\t}\n\t}\n\n\tif len(p) == 0 || ss == \"\" {\n\t\treturn ss, nil\n\t}\n\n\tif len(p)%2 != 0 {\n\t\treturn \"\", fmt.Errorf(\"uneven number of replacement pairs\")\n\t}\n\n\tkey := strings.Join(p, \"\\x00\")\n\treplacer, err := ns.replacerCache.GetOrCreate(key, func() (*strings.Replacer, error) {\n\t\treturn strings.NewReplacer(p...), nil\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn replacer.Replace(ss), nil\n}\n\n// SliceString slices a string by specifying a half-open range with\n// two indices, start and end. 1 and 4 creates a slice including elements 1 through 3.\n// The end index can be omitted, it defaults to the string's length.\nfunc (ns *Namespace) SliceString(a any, startEnd ...any) (string, error) {\n\taStr, err := cast.ToStringE(a)","sourceCodeStart":283,"sourceCodeEnd":319,"githubUrl":"https://github.com/gohugoio/hugo/blob/8a468df065a75c1c7cf9f6850f32148746590ea5/tpl/strings/strings.go#L283-L319","documentation":"`strings.ReplacePairs` builds a `strings.Replacer` from the collected pair list, which requires old/new values in matched pairs — an odd count means some old value has no replacement. Hugo validates `len(p)%2 != 0` before constructing the Replacer, since `strings.NewReplacer` would otherwise panic.","triggerScenarios":"An odd number of pair elements: inline form `{{ strings.ReplacePairs \"a\" \"b\" \"c\" $src }}` (three pair args), or slice form `{{ strings.ReplacePairs (slice \"a\" \"b\" \"c\") $src }}`. Empty pairs skip this check (the source is returned unchanged), so only genuinely odd counts trigger it.","commonSituations":"Forgetting the source string goes last, so the last replacement value gets consumed as the source and the remaining pairs become odd; dropping one element when editing a YAML list of pairs in front matter; programmatically appending to the pairs slice and missing a value.","solutions":["Count the pair elements — every old string needs a new string; add the missing element.","Check you didn't omit the trailing source argument: the last argument is always the source, not part of a pair.","If pairs come from a data file, validate the list length is even at the source."],"exampleFix":"<!-- before -->\n{{ strings.ReplacePairs (slice \"a\" \"A\" \"b\") $src }}\n<!-- after -->\n{{ strings.ReplacePairs (slice \"a\" \"A\" \"b\" \"B\") $src }}","handlingStrategy":"validation","validationCode":"{{ if eq (mod (len $pairs) 2) 0 }}{{ strings.Replacer $s $pairs }}{{ else }}{{ errorf \"replacement pairs uneven: %v\" $pairs }}{{ end }}","typeGuard":"func evenPairs(pairs []string) bool { return len(pairs)%2 == 0 }","tryCatchPattern":"out, err := ns.Replacer(args...)\nif err != nil {\n    // odd pair count — one old is missing its new; fix config, don't drop the last element\n}","preventionTips":["Check `len % 2 == 0` on replacement lists sourced from config/data","Define pairs as a list of two-element entries in config and flatten at use, making unevenness impossible","Fail the build with `errorf` on uneven pairs instead of silently truncating"],"tags":["hugo","go-templates","strings","arguments","validation"],"analyzedSha":"8a468df065a75c1c7cf9f6850f32148746590ea5","analyzedAt":"2026-07-31T21:23:07.045Z","schemaVersion":2}