Serve Angular i18n with clean URL's using Apache's mod_rewrite
Tags: javascript, angular, i18n, apache
Published: 2021-05-26 06:12:25

In Angular you have some great internationalisation ([i18n](https://angular.io/guide/i18n)) out of the box, which builds the nice compiled and minified bundle your used to. You have a compiled version per language. If you work with another framework which does the same thing (export a language per directory), this approach will work for you as well.

My problem: the separate directories give you nasty URL's, something like whatwhat.app/en/. Okay, so that's just looks but it can also be a problem with sharing links. For example when user A shares a link with user B, but they have different native languages it will be annoying that the opened link does not have the language correct language for the receiver.

In my case I wanted to make it a setting that the user can adjust, with a default language as a fallback. Easy to make and accessible because every page and link is always in your preferred language.

## Make it work with .htaccess, mod_rewrite and cookies

In the `.htaccess` file I added notes about what is happening if you are curious about that. Make sure to adjust `example_locale` which is the name of the cookie, `(en|nl|de)` for the allowed options and `[E=LANG:en]` will set the default language. To switch language, set a cookie and refresh the page from the app. That's it.

```
# Check if mod_rewrite module is available
<IfModule mod_rewrite.c>

  # Turn the rewrite engine on and set URI base
  RewriteEngine On
  RewriteBase /

  # Prevent recursive rewrites
  RewriteCond %{ENV:REDIRECT_STATUS} 200
  RewriteRule ^ - [L]
  
  # Check for the cookie, get value or set to default
  RewriteCond %{HTTP_COOKIE} example_locale=(en|nl|de)
  RewriteRule .? - [E=LANG:%1,S=1]
  RewriteRule .? - [E=LANG:en]
  
  # Check if file exists, if so serve it
  RewriteCond %{DOCUMENT_ROOT}/%{ENV:LANG}%{REQUEST_URI} -f
  RewriteRule ^ %{DOCUMENT_ROOT}/%{ENV:LANG}%{REQUEST_URI} [L]
  
  # Serve index by default, also when a file isn't found
  RewriteRule ^ %{DOCUMENT_ROOT}/%{ENV:LANG}/index.html

  # Disable browser caching to prevent serving old versions
  # It also solves issues when switching language
  <FilesMatch "\.(html|htm|js|json)$">
    <IfModule mod_headers.c>
      FileETag None
      Header unset ETag
      Header unset Pragma
      Header unset Cache-Control
      Header unset Last-Modified
      Header set Pragma "no-cache"
      Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"
      Header set Expires "Mon, 10 Apr 1972 00:00:00 GMT"
    </IfModule>
  </FilesMatch>

</IfModule>
```

To be honest, it has a downside. Caching is pretty much turned off. If you don't, the user can not switch languages after changing the cookie. Because the reload will hit the cache and serve the file for the old language. That is also the upside, you will have a bit more bandwith and load time, but if your product moves fast your visitors will likely be on the most recent version too.

When that's okay and you want to make your load times faster, look into lazy loading if you haven't already. It will split up your application and make the initial load much smaller. Only loading additional parts of the application when needed. 

Still angry with me? Please, I have one more option. You can also make multiple builds and combine them. The Angular build process creates the one hash for the build, for all languages. By doing this you have different hashes per language. Removing `|js|json` in the example above will make sure js/json files are cached, but will always fetch the `index.html` file and therefor load the prefered language. A bit more work to fix your CI, but still.

That's how I use Apache to serve different languages of my Angular app. Let me know if you have any questions, I am happy to help. Feedback is welcome too! I am pretty new to this writing thing. You can also reach out to me on [Twitter](https://twitter.com/timetcetera) 🙂

If you want to make your translation process easier and less time consuming, I made a tool for myself to let me focus on the translations. [What?!](https://whatwhat.app/) Yeah, that's the name, you can use it too if you want.