:: looking around irritably ::
Y'know, I just spent three hours at the library to give someone else a chance to answer this question :(
The underlying issue is that I think you're a little hazy on the difference between a Rewrite and a Redirect. You can use mod_rewrite for both, but they do different things. In particular, it's not entirely clear what you want the human user to see. Subdomain, query string, neither, both?
Line by line:
RewriteCond %{QUERY_STRING} !lang= [NC]
Good start. You want to make sure that the language query isn't already present, which would send you into an infinite loop of
?lang=de&lang=de&lang=de&lang=de...
until the server puts its foot down-- generally after ten iterations.
RewriteCond %{HTTP_HOST} ^(www\.)?(de|es)\.example\.com$ [NC]
You don't need the anchored ^(www\.)? element, since you're not capturing. In fact, if this is intended to be a Rewrite, coming after any redirects, there's no ? about it. At this point, the requests either starts with www. or it doesn't.
RewriteRule ^(.*)$ $1?lang=%2 [NC,QSA,R,L]
When I type in de.example.com/contact, it rewrites to de.example.com/contact?lang=de
No, it doesn't. It
redirects. The [R] flag converts the Rewrite into a temporary (302) Redirect. This is precisely what you don't want. Leave it out. It's doubly bad here because you didn't give the full protocol-plus-domain, so redirects will carry through with or without www, whichever way the original user requested it. Can you say Duplicate Content in three languages?
The [NC] flag is unnecessary here since you have not given any text to match.
Do you have different images, different stylesheets and so on, based on the language? Everything will run faster if you constrain the rule to requests with the appropriate "page" extension. Or no extension at all, if that's what you are doing. For example
RewriteRule ^([^.]*(/|\.php))$ {etc. --for pages in php}
or
RewriteRule ^([^.]*)$ {etc. --for extensionless pages}
Always put as much information as possible into the body of the Rule, so the server doesn't have to waste time checking Conditions all over again for every single request.
So my question is, is there a way to omit the ?lang=de at the end? A bit redundant.
Well, you put it there yourself. Can't blame the server for doing what you told it to. You explicitly said ?lang=%2 --and, to make double sure, you included a QSA flag to reappend any earlier queries.
All this is fine if you are doing a behind-the-scenes Rewrite. The upfront Redirect is a different issue. Which is where we came in.