{"record":{"id":"809346c4c4ab8ecb","repo":"Humanizr/Humanizer","slug":"inflection-subject-contains-text-outside-its-dec","errorCode":null,"errorMessage":"Inflection {subject} contains text outside its declared scripts.","messagePattern":"Inflection (.+?) contains text outside its declared scripts\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Humanizer.SourceGenerators/Generators/ProfileCatalogs/InflectionCatalogValidation.cs","lineNumber":253,"sourceCode":"\n            if (casing == \"lower-title-upper\")\n            {\n                if (!TryNormalizeSimpleLower(normalized, out var lower))\n                {\n                    throw new InvalidOperationException(\n                        $\"Inflection {subject} has an unsupported casing expansion.\");\n                }\n\n                normalized = lower;\n            }\n\n            var literal = allowStemPlaceholder\n                ? normalized.Replace(\"{stem}\", string.Empty)\n                : normalized;\n            if (literal.Length > 0 &&\n                !HasOnlyDeclaredScripts(literal, ownerScripts, allowNonLetters))\n            {\n                throw new InvalidOperationException(\n                    $\"Inflection {subject} contains text outside its declared scripts.\");\n            }\n\n            return normalized;\n        }\n\n        static bool TryNormalizeSimpleLower(string value, out string normalized)\n        {\n            var builder = new StringBuilder(value.Length);\n            for (var index = 0; index < value.Length;)\n            {\n                var scalarOffset = index;\n                var first = value[index++];\n                int scalar;\n                if (char.IsHighSurrogate(first))\n                {\n                    if (index >= value.Length || !char.IsLowSurrogate(value[index]))\n                    {","sourceCodeStart":235,"sourceCodeEnd":271,"githubUrl":"https://github.com/Humanizr/Humanizer/blob/ffc2b77c0f30d2fb176875841424379319d0ae9b/src/Humanizer.SourceGenerators/Generators/ProfileCatalogs/InflectionCatalogValidation.cs#L235-L271","documentation":"After NFC normalization and optional casing, authored text is checked against the inflection owner's declared Unicode scripts. Every letter and mark must belong to one of the scripts declared in the owner's 'scripts:' list. Characters outside the declared scripts (or non-letter characters when allowNonLetters is false) cause rejection.","triggerScenarios":"A lexeme or guard text contains characters from a Unicode script not listed in the owner's 'scripts:' array, or contains non-letter characters (digits, punctuation) when allowNonLetters is false. The HasOnlyDeclaredScripts method returns false for the literal.","commonSituations":"Mixing Latin loanwords into a Cyrillic-script locale without declaring 'latin' in scripts. Using digits or hyphens in form text when the field does not allow non-letters. Adding a new script to forms but forgetting to update the owner's scripts list.","solutions":["Add the missing Unicode script name to the owner's 'scripts:' list (e.g., add 'latin' alongside 'cyrillic').","Remove characters from the authored text that belong to undeclared scripts or are non-letters when not allowed.","If the field supports non-letters, verify allowStemPlaceholder/allowNonLetters is configured correctly for that call path."],"exampleFix":"# before\nowners:\n  - scripts: ['cyrillic']\nlexemes:\n  - singular:\n      accepted: ['PDF документ']\n# after\nowners:\n  - scripts: ['cyrillic', 'latin']\nlexemes:\n  - singular:\n      accepted: ['PDF документ']","handlingStrategy":"validation","validationCode":"# Before building, verify all letter characters belong to declared scripts.\npython3 -c \"\nimport yaml, sys, unicodedata\nfor f in sys.argv[1:]:\n    d = yaml.safe_load(open(f))\n    owners = (d.get('inflection',{}).get('owners') or [])\n    for owner in owners:\n        declared = set(owner.get('scripts') or [])\n        for lex in (owner.get('lexemes') or []):\n            for v in ((lex.get('singular') or {}).get('accepted') or []):\n                for ch in v:\n                    if ch.isalpha():\n                        script = unicodedata.name(ch,'').split()[0].lower()\n                        if script not in declared:\n                            print(f'{f}: char {ch!r} script {script!r} not in declared {declared}')\n\" src/Humanizer/Locales/*.yml","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Declare every script that appears in the locale's lexeme/rule text in the owner's scripts list.","When adding loanwords from another script, update the scripts array immediately.","Avoid non-letter characters (digits, symbols) in fields that set allowNonLetters=false."],"tags":["inflection","source-generator","scripts","unicode","yaml","build-time"],"backgroundTag":null,"analyzedSha":"ffc2b77c0f30d2fb176875841424379319d0ae9b","analyzedAt":"2026-08-13T21:42:34.584Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}