stalwartlabs/stalwart · error

{key}: {language} repeats the plural category {name:?}

Error message

{key}: {language} repeats the plural category {name:?}

What it means

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.

Source

Thrown at crates/common/build.rs:101

    for (key, translations) in locales {
        if !translations
            .values()
            .any(|value| split_plural_forms(value).is_some())
        {
            continue;
        }

        for (language, value) in translations {
            let Some(forms) = split_plural_forms(value) else {
                panic!(
                    "{key}: {language} has no plural categories while other languages do: {value:?}"
                );
            };
            let mut seen = HashSet::new();
            for (name, _) in &forms {
                if !seen.insert(*name) {
                    panic!("{key}: {language} repeats the plural category {name:?}");
                }
            }
            if !seen.contains("other") {
                panic!("{key}: {language} is missing the required \"other\" plural category");
            }
        }

        keys.insert(key.clone());
    }

    keys
}

fn plural_forms_literal(value: &str) -> String {
    let forms = split_plural_forms(value).expect("validated above");
    let other = forms
        .iter()
        .find(|(name, _)| *name == "other")

View on GitHub (pinned to e962003857)

Solutions

  1. Locate the key/language in i18n.yml from the panic message and remove the duplicate category segment, keeping the correct translation.
  2. Merge the two texts of the duplicated category into a single `category=text` segment.
  3. Re-run the build to confirm validation passes.

Example fix

# before
  fr: one=un article;one=une pièce;other=articles
# after
  fr: one=un article;other=articles
Defensive patterns

Strategy: validation

Validate before calling

let names: Vec<&str> = value.split(';').filter_map(|s| s.split_once('=').map(|(n, _)| n)).collect();
let dup: HashSet<_> = names.iter().filter(|n| names.iter().filter(|m| *m == *n).count() > 1).collect();
assert!(dup.is_empty(), "duplicate plural categories: {dup:?}");

Prevention

When it happens

Trigger: 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.

Common situations: Copy-paste errors when translating, manual merge of i18n.yml that duplicated a line segment, scripted edits that append forms without deduplicating.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of stalwartlabs/stalwart@e962003857 (2026-09-06). Data as JSON: /api/errors/30e2ce9b6e8e3b50. Report an issue: GitHub.