This project was generated with Angular CLI version 17.0.9.
I used approach described in this README for my web-site for tracking russian losses during the invasion of Ukraine. Deployed app on AWS
This app was created for example purposes for anyone seeking how to make Angular built-in i18n and SSR work together simultaneously in production and dev environment.
Kudos to Lostium team for providing guidance and answering my questions. Their example is a robust approach everyone should consider as first option.
Nevertheless, I came up with more clunky and basic solution with some minor disadvantages.
$ git clone https://github.com/Volodymyr-Mishyn/ng17-ssr-i18n-example.git
$ cd ng17-ssr-i18n-example
$ npm install
To run dev server for English version
$ npm run start
To run dev server for Ukrainian version
$ npm run start:uk
To run production server of Express with both locales
$ npm run build
$ npm run serve:ssr:ng17-ssr-i18n-example
To make your application work with ssr and i18n you'll have to make steps close to following
Creating server side rendering applications
$ ng new --ssr
To add SSR to an existing project
$ ng add @angular/ssr
Angular creates server.ts file with Express.js for Server-Side Rendering (SSR) We will need to modify this file later, depending on our locales.
All information about preparing your application for i18n, extracting strings for localization, and working with locale files is present on Angular i18n.
After preparing your files you'll have something like
messages.xlf, messages.{your-locale}.xlf, messages.uk.xlf,
Configure i18n in your angular.json Provide your base locale and other locales you prepared
"i18n": {
"sourceLocale": {
"code": "en-US",
"baseHref": "/en/"
},
"locales": {
"uk": {
"translation": "src/locale/messages.uk.xlf",
},
}
},
Setup localization during build time
"architect": {
"build": {
"options": {
"localize": true,
}
}
}
Setup additional dev configuration for languages you want to serve with dev server
"configurations": {
"development-uk": {
"localize": ["uk"],
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
}
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "ng17-ssr-i18n-example:build:production"
},
"development": {
"buildTarget": "ng17-ssr-i18n-example:build:development"
},
"development-uk": {
"buildTarget": "ng17-ssr-i18n-example:build:development-uk"
}
},
"defaultConfiguration": "development"
}
Add script to run your preferred languages to your package.json for quality of life improvement:)
"scripts": {
"start": "ng serve",
"start:uk": "ng serve --configuration=development-uk",
},
When Angular scaffolds ssr for your application it creates server.ts.
But after adding localization your dist
folder will have another structure
- browser/
- en-US/
- uk/
- server/
- en-US/
- uk/
- 3rdpartylicenses.txt
- prerendered-routes.json
but the server.ts is generated without respecting this locales nested structure. So we have to make changes to it
- Modify app method to have locale parameter
export function app(locale: string): express.Express {
/**/
}
- Use that parameter to build paths respecting locale
const serverDistFolder = resolve(dirname(fileURLToPath(import.meta.url)), "../", locale);
const browserDistFolder = resolve(serverDistFolder, "../../browser/", locale);
const indexHtml = join(serverDistFolder, "index.server.html");
- Configure server with this paths
server.set("view engine", "html");
server.set("views", browserDistFolder + "/");
// Serve static files from /browser
server.get(
`*.*`,
express.static(browserDistFolder + "/", {
maxAge: "1y",
})
);
// All regular routes use the Angular engine
server.get(`*`, (req, res, next) => {
const { protocol, originalUrl, headers, baseUrl } = req;
commonEngine
.render({
bootstrap,
documentFilePath: indexHtml,
url: `${protocol}://${headers.host}${originalUrl}`,
publicPath: browserDistFolder + "/",
providers: [
{ provide: APP_BASE_HREF, useValue: baseUrl },
//provide locale to the app
{ provide: LOCALE_ID, useValue: locale },
],
})
.then((html) => res.send(html))
.catch((err) => next(err));
});
- Create instance of Express server for every locale and set it to be responsible for your route
server.use("/en", app("en-US"));
server.use("/uk", app("uk"));
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
- (Optional) Add redirects
server.get("/", (req, res) => {
const { headers, protocol } = req;
res.redirect(`${protocol}://${headers.host}/en`);
});
server.get("*", (req, res) => {
const { headers, protocol } = req;
res.redirect(`${protocol}://${headers.host}/en`);
});
Important note : Your basehref-s should be the same in your built index.html files and in Express server setup
/en/
and /en
,
/uk/
and /uk
When angular builds your application each locale folder has it's own server file. The interesting part, those files don't differ for locales, they are the same. So you can run
$ npm run build
$ node dist/ng17-ssr-i18n-example/server/en-US/server.mjs
or
$ node dist/ng17-ssr-i18n-example/server/{your-locale}/server.mjs
and it would lead to same result - same express server with all your configured locales will run. Congratulations!