{"record":{"id":"30e2ce9b6e8e3b50","repo":"stalwartlabs/stalwart","slug":"key-language-repeats-the-plural-category-nam","errorCode":null,"errorMessage":"{key}: {language} repeats the plural category {name:?}","messagePattern":"(.+?): (.+?) repeats the plural category (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/common/build.rs","lineNumber":101,"sourceCode":"\n    for (key, translations) in locales {\n        if !translations\n            .values()\n            .any(|value| split_plural_forms(value).is_some())\n        {\n            continue;\n        }\n\n        for (language, value) in translations {\n            let Some(forms) = split_plural_forms(value) else {\n                panic!(\n                    \"{key}: {language} has no plural categories while other languages do: {value:?}\"\n                );\n            };\n            let mut seen = HashSet::new();\n            for (name, _) in &forms {\n                if !seen.insert(*name) {\n                    panic!(\"{key}: {language} repeats the plural category {name:?}\");\n                }\n            }\n            if !seen.contains(\"other\") {\n                panic!(\"{key}: {language} is missing the required \\\"other\\\" plural category\");\n            }\n        }\n\n        keys.insert(key.clone());\n    }\n\n    keys\n}\n\nfn plural_forms_literal(value: &str) -> String {\n    let forms = split_plural_forms(value).expect(\"validated above\");\n    let other = forms\n        .iter()\n        .find(|(name, _)| *name == \"other\")","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/stalwartlabs/stalwart/blob/e96200385781a6a9995a8b839ac27d6c75a983ee/crates/common/build.rs#L83-L119","documentation":"The build script validates that within a single translation value no plural category is repeated. It walks the `category=text` segments of a value, inserting each category name into a set; a duplicate name means ambiguous translations for one category, so it panics naming the key, language, and duplicated category. This prevents silently losing one of the two conflicting forms during code generation.","triggerScenarios":"A value in `resources/locales/i18n.yml` contains the same plural category twice, e.g. `one=an item;one=un article;other=items` — split_plural_forms yields two entries named `one` and the `seen.insert` check fails.","commonSituations":"Copy-paste errors when translating, manual merge of i18n.yml that duplicated a line segment, scripted edits that append forms without deduplicating.","solutions":["Locate the key/language in i18n.yml from the panic message and remove the duplicate category segment, keeping the correct translation.","Merge the two texts of the duplicated category into a single `category=text` segment.","Re-run the build to confirm validation passes."],"exampleFix":"# before\n  fr: one=un article;one=une pièce;other=articles\n# after\n  fr: one=un article;other=articles","handlingStrategy":"validation","validationCode":"let names: Vec<&str> = value.split(';').filter_map(|s| s.split_once('=').map(|(n, _)| n)).collect();\nlet dup: HashSet<_> = names.iter().filter(|n| names.iter().filter(|m| *m == *n).count() > 1).collect();\nassert!(dup.is_empty(), \"duplicate plural categories: {dup:?}\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never duplicate a category segment within one value; merge texts instead.","Run a YAML linter/pre-commit hook over i18n.yml.","Review merge conflicts in i18n.yml line by line."],"tags":["build-script","i18n","pluralization","duplicate"],"backgroundTag":"schema-validation-failed","analyzedSha":"e96200385781a6a9995a8b839ac27d6c75a983ee","analyzedAt":"2026-09-06T22:07:17.982Z","contentChangedAt":"2026-09-06T22:07:17.982Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}