For projects that are user facing and so need to do localization, you do have a few options, including both gettext and ICU at opposite ends of the scale of "How difficult is your problem/ how much work do you want to do?"
Gettext is just going to let you substitute translations like "X {1} Y {2}" in one language versus "YorX: {2}or{1}" in another language, whereas ICU understands how to render the Japanese form of today's date and which is the correct plural form for 38 of this thing in Russian (but it's still on you to prepare all the plural variants for each type of thing you want to quantify, ICU just tells you which of them to use)
The design of Rust's standard library is to be as minimal as possible. Full localization support would be too much for a tiny standard library such as Rust's. Contrast this to Java or Go.
In the library ecosystem, there are full implementations of fluent available, and the compiler is being translated. It's not ergonomic to use yet, meaning you have to put the strings into a separate .ftl file (maybe it will never be), but it's quite powerful and developed by a lot of experts on the topic.
gettext seems pretty difficult to use in Rust - there's no obvious way to apply a runtime format string "My name is {1}".
https://blog.hackeriet.no/rust-and-translation-files/ describes using gettext from Rust but not how to handle format strings.
Maybe if you bake in assumptions about the deployed environment at compile time you can catch errors earlier for your specific environment, but all environments? No. That’s not the responsibility of the standard library and given the language lacks a single specific runtime target- definitely not the responsibility of the language/compiler. This is instead a classic use case for tests and opinionated (optional/third party) libraries instead.
Format strings, for example- are essentially compile time macros in rust in order to avoid all the fun kinds of string handling bugs that C’s implementation allowed- but that means no runtime customization without also defining were alternate formats live (and how they’re verified, etc). Not supporting multiple languages is a feature, not a bug. If you need multi language support, you need to a library to define those semantics in a way that fits or use case.
However, you don't have to use format! to format stuff, you would presumably want an API which better reflects the runtime errors you can now encounter, such as wrong number of arguments, wrong order of arguments, incompatible formats.
You are probably aware, but there is a currently ongoing project to move the format_args macro further into the compiler (it's a builtin macro right now, but not doing much that a proc macro can't do), to do its work during AST->HIR lowering. On top of that, some optimizations are proposed that would be impossible to implement on macros today.
For some of those optimizations, a primitive to allow proc macros to expand macros of their own would make it possible to have with a pure-macro solution. Even just the ability for macros to say that they want their input to be expanded instead of receiving pre-expanded input would be enough. These primitives are not available today, but are possible future extensions.