Parse your strings, kids.
Parse your strings, kids.
“User strings vs. application strings” is too coarse. You just need to enforce types (a type for SQL – see query builders, a type for HTML – see MarkupSafe, etc.) and provide safe constructors for those types. Safe syntactic sugar for those is supported by JavaScript (template strings), Rust (macros), C++ (overloadable string literals), Haskell (overloadable string literals and Template Haskell), and probably plenty of other modern languages. For the others, explicit type wrappers are generally enough (like the aforementioned MarkupSafe in Python) – the only thing that’s lacking is enforcement by libraries.
Most tools and practices miss that point. This leads to creation of general-purpose (not type-safe) templating systems that can't automatically enforce correct escaping everywhere by default, so they leave it to programmers to do it manually where needed, which is error prone and ensures permanent OWASP entry as long as it exists.
Also it's impossible to convince programmers to always implement "redundant" escaping for "harmless" values (such as IDs which are assumed to always be alphanumeric), which is a vulnerability waiting to be exploited (e.g. your policy is not to allow in '"' usernames, so you think you never need escape it, but then some code reads a username from a query string argument and game over).
Functions that do risky things (Database, System calls, etc.) only accept LockStrings and are responsible for escaping, so all you have to do is provide the placeholders.
Worse, it is not uncommon to find that the layer you are using to keep things structured becomes a major source of complexity in the application.