The goal of writing better error messages isn't to help the people who never read error messages, it's to help the people who do and who you never have to hear from.
The trick that I've found is that each error message needs to be unique... not just the stack trace, but the actual wording of the message leading up to that.
Get a screenshot or the exact verbatim of it, and you can identify exactly where in the code it originated.
User reports are unreliable, but when I can pinpoint where the message originated from, it massively cuts down on the troubleshooting time.
About that, the number of developers that can’t read, or even understand the value of, a stack trace is also astonishing.
If only I had a penny every time someone sent me a “log of the error”, that only contains the final line with the unhelpful message saying nothing but KeyError.
At prior work we removed stack traces from the default error output because it was thought to "scare" too many users.
Then for years almost without fail when an error was pasted into a GH issue it would include the big "If submitting a bug report, please include the full stack trace at /var/log/stacktrace.out" message--without the stacktrace. I added some whitespace around it and all caps to it and still nobody read it.
I've met multiple "web developers" (actually working on the backend or "full-stack", building API servers and whatnot) who came complaining about this or that server being "unreachable" and could I check it's up / whether the firewall allows them through. Only to find they were getting HTTP 404 errors or the like. Which were explicit in the errors they'd show me.
A useful thing here is not just to include a unique error code for the type of error (usually numeric), but also to generate some kind of short Base32 or similar hash and print that right next to the error message while logging it to your normal back end. Then whether people send you a screen shot, copy/paste, whatever, you can easily search the logs to find the exact event that occurred.
Yes, that type of thing is pretty useful for linters. These error codes act as identifiers if you need to google them and whenever you need to configure the linter the way you like it or for one-off exceptions.
Include random numbers. "Error 7743929" is super easy to track down (grep -r 7743929 takes 2 seconds to type), you don't need a NATO alphabet to understand what they're saying on the phone in order to be able to search it correctly, its general purpose is understood internationally, and it won't change between versions (like when you'd encode a file name and line number, for example). When I first figured this out at, idk, 17 years old and mentioned the idea in a game making forum, people called me crazy, but I still use it and don't know of any better system.
Of course, this is alongside an actual error message to help the user help themselves. This is just to trace the line where it originated, which already helps a lot for small software projects like I make.
It turns out translating error messages is controversial.
Users, upon hitting an error, often go check Stack Overflow. If you localize your error messages, you Balkanize the collective wisdom on how to address the error (which will always be larger than your team's ability to troubleshoot errors and offer correctives in your documentation and FAQs).
I used to lean on line numbers, but those quickly fall out of sync with deployed code and what's currently checked out and available for immediate debugging. I've also switched to using unique text you mention as it will always find the place in the code regardless if it has been moved.