Essentially you need to mark every error with an error code, and that error code needs to have a language specific translation/format string.
The backend as such is unaware of language. Any error message generated alongside it is purely for internal consumption by dev and support.
Where the error code is translated into a consumer language specific error message depends on largely on taste.
- It can be translated at the api layer. This requires that the server is aware of the language the user is using, which may not be knowledge that it has.
- It can be translated at the client layer. This requires that the client have the full list of possible errors - which can be quite large and may require frequent updates.
I'd recommend two tables for translation.
- The first table translates from source error code to a client error code.
- The second table translates from client error code to human readable language text.
Don't forget to have a general error, error so that new errors added by devs always have some presentation for the user.
As Ralf Kleberhoff has pointed out below, the Error Code does not need to be an integer. It can for example be a short string.
You don't even have to capitalise it as I did, but it is generally a good idea to have a naming convention.