s.el's s-lex-format is really what you want, but if you want to actually be able to put code inside the substitution blocks and not just variable names, I wrote this as a proof of concept.
(defmacro fmt (str) "Elisp string interpolation for any expression." (let ((exprs nil)) (with-temp-buffer (insert str) (goto-char 1) (while (re-search-forward "#{" nil t 1) (let ((here (point)) (emptyp (eql (char-after) ?}))) (unless emptyp (push (read (buffer-substring (point) (progn (forward-sexp 1) (point)))) exprs)) (delete-region (- here 2) (progn (search-forward "}") (point))) (unless emptyp (insert "%s")) (ignore-errors (forward-char 1)))) (append (list 'format (buffer-string)) (reverse exprs))))) ;; demo with variable and code substitution (fmt "My name is #{user-full-name}, I am running Emacs #{(if (display-graphic-p) \"with a GUI\" \"in a terminal\")}.") ;; results in "My name is Jordon Biondo, I am running Emacs with a GUI."
You can even embed an fmt call inside another fmt if you're crazy
(fmt "#{(fmt\"#{(fmt\\\"#{user-full-name}\\\")}\")}") ;; => "Jordon Biondo"
The code just expands to a format call so all the substitutions are done in order and evaluated at run time.
(cl-prettyexpand '(fmt "Hello, I'm running Emacs #{emacs-version} on a #{system-type} machine with #{(length (window-list))} open windows.")) ;; expands to (format "Hello, I'm running Emacs %s on a %s machine with %s open windows." emacs-version system-type (length (window-list)))
Improvements could be made with what format type is used instead of always using %s, but that would have to be done at runtime and would add overhead but could be done by surrounding all the format args in a function call that nicely formats things nicely based on type but really the only scenario where you would want that is probably floats and you could even do a (format "%f" float) in the substitution is you were desperate.
If I work on it more, I'm more likely to update this gist instead of this answer. https://gist.github.com/jordonbiondo/c4e22b4289be130bc59b