This is my solution to solve this for multiple projects:
nginx.conf
http {
server {
# Sets our default language (it's the angular template default language)
set $defaultLang "de";
listen 80;
root /usr/share/nginx/html;
index index.html;
include /etc/nginx/mime.types;
################## IMPORTANT (don't change this) ##################
# Make sure when routing to location, server uses the correct angular project subfolder
# Matches the following urls:
# http://localhost/de
# http://localhost/de/
# http://localhost/de/login
# http://localhost/notexist/login => In this case, try_files doesn't found a matching index.html and jumps into the @languageFallback
location ~ "^(/([a-z]{2,2})/)(/?.*)?$" {
try_files $uri $uri /$2/index.html @languageFallback;
}
# Make sure when routing to the root the root index is used (and we redirect through the small JS script -> redirect.js)
# Matches the following urls:
# http://localhost
# http://localhost/
location / {
try_files $uri $uri/ /index.html;
}
# Language fallback which is used when user tries to open a language which doesn't exist
# E.g When user trying to open http://localhost/notexist/login but it doesnt exist, then we rewrite the url to
# http://localhost/de/login
location @languageFallback {
rewrite "^(/([a-z]{2,2})/)(/?.*)?$" $scheme://$http_host/$defaultLang/$3 last;
}
}
}
Then i have an additional index.html with that small script inside, which is copied to the nginx root:
additional index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<title></title>
<script>
(function()
{
let redirectUrl;
const supportedLanguages = ['de', 'en'];
const fallbackLanguage = 'de';
// Read browser locale and use this as default language (only when no locale in localstorage was found)
let locale = (navigator.language || navigator['userLanguage']).slice(0, 2);
const storedLocale = localStorage.getItem('locale');
console.info('BROWSER LOCALE: ', locale);
console.info('STORED LOCALE: ', storedLocale);
//Check if a locale was already set in localstorage and use this or set the default language by default
//and browsers locale is not supported by app we fallback to the fallback language
if (!storedLocale)
{
if (supportedLanguages.indexOf(locale) === -1)
{
locale = fallbackLanguage;
}
}
else
{
locale = storedLocale;
}
redirectUrl = location.origin + '/' + locale + '/';
console.info('REDIRECT TO: ', redirectUrl);
// Redirect to correct language
location.replace(redirectUrl);
})();
</script>
</head>
<body>
</body>
</html>
This file is used to route a user to the correct language subproject depending on its browser language or when the user change the language within the application it's stored in the localstorage and this language is used with higher priority.
And my angular.json
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "",
"projects": {
"my-project": {
"i18n": {
"sourceLocale": {
"code": "de",
"baseHref": "/"
},
"locales": {
"en": {
"translation": "src/locales/messages.en.xlf",
"baseHref": "/"
}
}
},
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "sg",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"aot": true,
"outputHashing": "all",
"outputPath": "dist/my-project",
"resourcesOutputPath": "assets/fonts",
"baseHref": "/",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": [],
"stylePreprocessorOptions": {
"includePaths": [
"src/app"
]
}
},
"configurations": {
"production": {
"optimization": true,
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"i18nMissingTranslation": "error",
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb"
}
]
},
"dev": {
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "6kb"
}
],
"i18nMissingTranslation": "error"
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "my-project:build"
},
"configurations": {
"dev": {
"browserTarget": "my-project:build:dev"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "my-project:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"styles": [
"src/styles.scss"
],
"scripts": [],
"assets": [
"src/assets"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"xliffmerge": {
"builder": "@ngx-i18nsupport/tooling:xliffmerge",
"options": {
"xliffmergeOptions": {
"srcDir": "src/locales",
"genDir": "src/locales",
"i18nFile": "messages.xlf",
"i18nBaseFile": "messages",
"i18nFormat": "xlf",
"encoding": "UTF-8",
"defaultLanguage": "de",
"languages": [
"en"
],
"removeUnusedIds": true,
"supportNgxTranslate": false,
"ngxTranslateExtractionPattern": "@@|ngx-translate",
"useSourceAsTarget": true,
"targetPraefix": "",
"targetSuffix": "",
"beautifyOutput": true,
"allowIdChange": false,
"autotranslate": false,
"apikey": "",
"apikeyfile": "",
"verbose": false,
"quiet": false
}
}
}
}
}
},
"defaultProject": "my-project"
}
And finally some important npm script for that stuff from my package.json
"build": "ng build --prod --localize",
"i18n": "ng xi18n --format=xlf --output-path=src/locales --out-file=messages.xlf",
"xliffmerge": "ng run my-project:xliffmerge",
"translate": "npm run i18n; npm run xliffmerge"