import { environment } from '@ghostfolio/api/environments/environment'; import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; import { DEFAULT_LANGUAGE_CODE, STORYBOOK_PATH, SUPPORTED_LANGUAGE_CODES } from '@ghostfolio/common/config'; import { DATE_FORMAT, interpolate } from '@ghostfolio/common/helper'; import { Injectable, Logger, NestMiddleware } from '@nestjs/common'; import { format } from 'date-fns'; import { NextFunction, Request, Response } from 'express'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; const title = 'Ghostfolio'; const locales = { '/de/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt': { featureGraphicPath: 'assets/images/blog/ghostfolio-x-sackgeld.png', title: `Ghostfolio auf Sackgeld.com vorgestellt - ${title}` }, '/en/blog/2022/08/500-stars-on-github': { featureGraphicPath: 'assets/images/blog/500-stars-on-github.jpg', title: `500 Stars - ${title}` }, '/en/blog/2022/10/hacktoberfest-2022': { featureGraphicPath: 'assets/images/blog/hacktoberfest-2022.png', title: `Hacktoberfest 2022 - ${title}` }, '/en/blog/2022/12/the-importance-of-tracking-your-personal-finances': { featureGraphicPath: 'assets/images/blog/20221226.jpg', title: `The importance of tracking your personal finances - ${title}` }, '/en/blog/2023/02/ghostfolio-meets-umbrel': { featureGraphicPath: 'assets/images/blog/ghostfolio-x-umbrel.png', title: `Ghostfolio meets Umbrel - ${title}` }, '/en/blog/2023/03/ghostfolio-reaches-1000-stars-on-github': { featureGraphicPath: 'assets/images/blog/1000-stars-on-github.jpg', title: `Ghostfolio reaches 1’000 Stars on GitHub - ${title}` }, '/en/blog/2023/05/unlock-your-financial-potential-with-ghostfolio': { featureGraphicPath: 'assets/images/blog/20230520.jpg', title: `Unlock your Financial Potential with Ghostfolio - ${title}` }, '/en/blog/2023/07/exploring-the-path-to-fire': { featureGraphicPath: 'assets/images/blog/20230701.jpg', title: `Exploring the Path to FIRE - ${title}` }, '/en/blog/2023/08/ghostfolio-joins-oss-friends': { featureGraphicPath: 'assets/images/blog/ghostfolio-joins-oss-friends.png', title: `Ghostfolio joins OSS Friends - ${title}` }, '/en/blog/2023/09/ghostfolio-2': { featureGraphicPath: 'assets/images/blog/ghostfolio-2.jpg', title: `Announcing Ghostfolio 2.0 - ${title}` }, '/en/blog/2023/09/hacktoberfest-2023': { featureGraphicPath: 'assets/images/blog/hacktoberfest-2023.png', title: `Hacktoberfest 2023 - ${title}` }, '/en/blog/2023/11/black-week-2023': { featureGraphicPath: 'assets/images/blog/black-week-2023.jpg', title: `Black Week 2023 - ${title}` }, '/en/blog/2023/11/hacktoberfest-2023-debriefing': { featureGraphicPath: 'assets/images/blog/hacktoberfest-2023.png', title: `Hacktoberfest 2023 Debriefing - ${title}` }, '/en/blog/2024/09/hacktoberfest-2024': { featureGraphicPath: 'assets/images/blog/hacktoberfest-2024.png', title: `Hacktoberfest 2024 - ${title}` }, '/en/blog/2024/11/black-weeks-2024': { featureGraphicPath: 'assets/images/blog/black-weeks-2024.jpg', title: `Black Weeks 2024 - ${title}` }, '/en/blog/2025/09/hacktoberfest-2025': { featureGraphicPath: 'assets/images/blog/hacktoberfest-2025.png', title: `Hacktoberfest 2025 - ${title}` }, '/en/blog/2025/11/black-weeks-2025': { featureGraphicPath: 'assets/images/blog/black-weeks-2025.jpg', title: `Black Weeks 2025 - ${title}` }, '/en/blog/2026/04/ghostfolio-3': { featureGraphicPath: 'assets/images/blog/ghostfolio-3.jpg', title: `Announcing Ghostfolio 3.0 - ${title}` } }; @Injectable() export class HtmlTemplateMiddleware implements NestMiddleware { private readonly logger = new Logger(HtmlTemplateMiddleware.name); private indexHtmlMap: { [languageCode: string]: string } = {}; public constructor(private readonly i18nService: I18nService) { if (!environment.production) { return; } this.indexHtmlMap = SUPPORTED_LANGUAGE_CODES.reduce((map, languageCode) => { const indexHtmlPath = join( __dirname, '..', 'client', languageCode, 'index.html' ); try { // Restore the interpolation token which the template replaces with a // static fallback title to avoid showing an unresolved template // literal when served without interpolation (e.g. by the service worker) map[languageCode] = readFileSync(indexHtmlPath, 'utf8').replace( /