{"record":{"id":"915e7c4422487afe","repo":"stalwartlabs/stalwart","slug":"key-language-has-no-plural-categories-while-o","errorCode":null,"errorMessage":"{key}: {language} has no plural categories while other languages do: {value:?}","messagePattern":"(.+?): (.+?) has no plural categories while other languages do: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/common/build.rs","lineNumber":94,"sourceCode":"                .filter(|(name, _)| PLURAL_CATEGORIES.contains(name))\n        })\n        .collect()\n}\n\nfn plural_keys(locales: &HashMap<String, HashMap<String, String>>) -> HashSet<String> {\n    let mut keys = HashSet::new();\n\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","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/stalwartlabs/stalwart/blob/e96200385781a6a9995a8b839ac27d6c75a983ee/crates/common/build.rs#L76-L112","documentation":"During locale code generation, the build script detects translation keys that use plural categories (`one=...;other=...`) in at least one language, and then requires every language to provide the same plural-form syntax. If a language's value cannot be split into `category=text` segments (split_plural_forms returns None), it panics naming the key, language, and offending value. This keeps plural handling consistent across locales at build time.","triggerScenarios":"Editing `resources/locales/i18n.yml` so that one language for a pluralized key contains a plain string (no `cat=...;cat=...` segments), while other languages for that key contain valid plural forms. Also triggered by malformed syntax such as missing `=`, or using category names outside the allowed set (zero, one, two, few, many, other).","commonSituations":"A translator pastes a plain sentence into a field that is pluralized elsewhere; a new language entry is added as simple text while the key is plural in English; someone edits the value and accidentally breaks the `category=value;` format.","solutions":["Open i18n.yml at the key named in the panic and rewrite the `{language}` value using `zero=/one=/two=/few=/many=/other=` segments separated by `;`.","Ensure at least an `other=` category is present for that language.","If the key should not be pluralized at all, convert ALL languages' values for that key to plain strings.","Only use the six valid plural category names; anything else fails the split."],"exampleFix":"# before\nkey.name:\n  en: one={count} item;other={count} items\n  de: {count} Artikel\n# after\nkey.name:\n  en: one={count} item;other={count} items\n  de: one={count} Artikel;other={count} Artikel","handlingStrategy":"validation","validationCode":"// validate every pluralized value has cat=text segments before committing i18n.yml\nfor (key, translations) in &locales {\n    if translations.values().any(|v| split_plural_forms(v).is_some()) {\n        for (lang, v) in translations {\n            assert!(split_plural_forms(v).is_some(), \"{key}: {lang} lacks plural forms\");\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"// build-time panic; run the parser as a unit test to get a clean failure:\n#[test]\nfn i18n_yaml_plural_consistency() { /* call plural_keys(&locales) and assert no panic */ }","preventionTips":["Follow the `category=text;category=text` format exactly, using only zero/one/two/few/many/other.","Copy an existing language's structure when adding a new locale.","Add a CI job that runs `cargo build -p common` to catch i18n.yml regressions early."],"tags":["build-script","i18n","pluralization","validation"],"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-14T05:17:10.506Z"}