From 4c9ba9af83639982380243b977d006d032c9ee80 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 27 Aug 2026 18:54:46 +0200 Subject: [PATCH] Task/improve language localization of asset classes in AI prompt (#7729) * Improve language localization of asset classes * Update changelog --- CHANGELOG.md | 2 + apps/api/src/app/endpoints/ai/ai.service.ts | 42 ++++++++++++++++++- .../src/app/endpoints/mcp/mcp.controller.ts | 2 +- apps/api/src/services/i18n/i18n.service.ts | 10 ++++- apps/client/src/app/pages/i18n/i18n-page.html | 17 ++++++++ 5 files changed, 68 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2cd64b0e..7e73e73ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Improved the language localization of the asset classes and asset sub classes in the holdings table of the _Copy AI prompt to clipboard for analysis_ action on the analysis page (experimental) +- Improved the language localization of the asset classes and asset sub classes in the holdings table of the _Copy portfolio data to clipboard for AI prompt_ action on the analysis page (experimental) - Improved the logging of the `web_fetch` tool in the `FetchService` ## 3.61.0 - 2026-08-25 diff --git a/apps/api/src/app/endpoints/ai/ai.service.ts b/apps/api/src/app/endpoints/ai/ai.service.ts index 5c49d74cd..4c6e096b8 100644 --- a/apps/api/src/app/endpoints/ai/ai.service.ts +++ b/apps/api/src/app/endpoints/ai/ai.service.ts @@ -1,5 +1,6 @@ import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { PROPERTY_API_KEY_OPENROUTER, @@ -11,6 +12,7 @@ import type { AiPromptMode } from '@ghostfolio/common/types'; import { Injectable } from '@nestjs/common'; import { createOpenRouter } from '@openrouter/ai-sdk-provider'; +import { AssetClass, AssetSubClass } from '@prisma/client'; import { generateText } from 'ai'; import { format } from 'date-fns'; import type { ColumnDescriptor } from 'tablemark'; @@ -44,6 +46,7 @@ export class AiService { public constructor( private readonly configurationService: ConfigurationService, + private readonly i18nService: I18nService, private readonly portfolioService: PortfolioService, private readonly propertyService: PropertyService ) {} @@ -103,6 +106,18 @@ export class AiService { return { name, align: align ?? 'left' }; }); + const assetClassTranslations = this.getEnumTranslations({ + languageCode, + id: 'assetClass', + values: Object.values(AssetClass) + }); + + const assetSubClassTranslations = this.getEnumTranslations({ + languageCode, + id: 'assetSubClass', + values: Object.values(AssetSubClass) + }); + const holdingsTableRows = Object.values(holdings) .sort((a, b) => { return b.allocationInPercentage - a.allocationInPercentage; @@ -132,11 +147,11 @@ export class AiService { break; case 'ASSET_CLASS': - row[name] = assetClass ?? ''; + row[name] = assetClassTranslations[assetClass] ?? ''; break; case 'ASSET_SUB_CLASS': - row[name] = assetSubClass ?? ''; + row[name] = assetSubClassTranslations[assetSubClass] ?? ''; break; case 'CURRENCY': @@ -202,4 +217,27 @@ export class AiService { `Provide your answer in the following language: ${languageCode}.` ].join('\n'); } + + private getEnumTranslations({ + id, + languageCode, + values + }: { + id: string; + languageCode: string; + values: T[]; + }) { + return values.reduce( + (translations, value) => { + translations[value] = + this.i18nService.getTranslation({ + languageCode, + id: `${id}.${value}` + }) || value; + + return translations; + }, + {} as Record + ); + } } diff --git a/apps/api/src/app/endpoints/mcp/mcp.controller.ts b/apps/api/src/app/endpoints/mcp/mcp.controller.ts index ce4205748..3fc93219a 100644 --- a/apps/api/src/app/endpoints/mcp/mcp.controller.ts +++ b/apps/api/src/app/endpoints/mcp/mcp.controller.ts @@ -32,7 +32,7 @@ export class GhostfolioMcpController { const prompt = await this.aiService.getPrompt({ filters, userId, - languageCode: userSettings.language ?? DEFAULT_LANGUAGE_CODE, + languageCode: DEFAULT_LANGUAGE_CODE, mode: 'portfolio', userCurrency: userSettings.baseCurrency }); diff --git a/apps/api/src/services/i18n/i18n.service.ts b/apps/api/src/services/i18n/i18n.service.ts index 83eb3a3ac..cd8f85369 100644 --- a/apps/api/src/services/i18n/i18n.service.ts +++ b/apps/api/src/services/i18n/i18n.service.ts @@ -25,15 +25,21 @@ export class I18nService implements OnModuleInit { languageCode: string; placeholders?: Record; }): string { - const $ = this.translations[languageCode]; + const languageCodeToUse = this.translations[languageCode] + ? languageCode + : DEFAULT_LANGUAGE_CODE; + + const $ = this.translations[languageCodeToUse]; if (!$) { this.logger.warn(`Translation not found for locale '${languageCode}'`); + + return ''; } let translatedText = $( `trans-unit[id="${id}"] > ${ - languageCode === DEFAULT_LANGUAGE_CODE ? 'source' : 'target' + languageCodeToUse === DEFAULT_LANGUAGE_CODE ? 'source' : 'target' }` ).text(); diff --git a/apps/client/src/app/pages/i18n/i18n-page.html b/apps/client/src/app/pages/i18n/i18n-page.html index 3c2558ec5..e3fedde9a 100644 --- a/apps/client/src/app/pages/i18n/i18n-page.html +++ b/apps/client/src/app/pages/i18n/i18n-page.html @@ -1,6 +1,23 @@
    +
  • Alternative Investment
  • +
  • Commodity
  • +
  • Equity
  • +
  • Fixed Income
  • +
  • Liquidity
  • +
  • Real Estate
  • +
  • Bond
  • +
  • Cash
  • +
  • Collectible
  • +
  • Commodity
  • +
  • Cryptocurrency
  • +
  • ETF
  • +
  • Loan
  • +
  • Mutual Fund
  • +
  • Precious Metal
  • +
  • Private Equity
  • +
  • Stock
  • Ghostfolio is a personal finance dashboard to keep track of your net worth including cash, stocks, ETFs and cryptocurrencies across multiple