Browse Source

Task/improve language localization of asset classes in AI prompt (#7729)

* Improve language localization of asset classes

* Update changelog
pull/7713/head
Thomas Kaul 23 hours ago
committed by GitHub
parent
commit
4c9ba9af83
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 42
      apps/api/src/app/endpoints/ai/ai.service.ts
  3. 2
      apps/api/src/app/endpoints/mcp/mcp.controller.ts
  4. 10
      apps/api/src/services/i18n/i18n.service.ts
  5. 17
      apps/client/src/app/pages/i18n/i18n-page.html

2
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

42
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<T extends string>({
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<T, string>
);
}
}

2
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
});

10
apps/api/src/services/i18n/i18n.service.ts

@ -25,15 +25,21 @@ export class I18nService implements OnModuleInit {
languageCode: string;
placeholders?: Record<string, string | number>;
}): 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();

17
apps/client/src/app/pages/i18n/i18n-page.html

@ -1,6 +1,23 @@
<div class="container">
<div class="row">
<ul>
<li i18n="@@assetClass.ALTERNATIVE_INVESTMENT">Alternative Investment</li>
<li i18n="@@assetClass.COMMODITY">Commodity</li>
<li i18n="@@assetClass.EQUITY">Equity</li>
<li i18n="@@assetClass.FIXED_INCOME">Fixed Income</li>
<li i18n="@@assetClass.LIQUIDITY">Liquidity</li>
<li i18n="@@assetClass.REAL_ESTATE">Real Estate</li>
<li i18n="@@assetSubClass.BOND">Bond</li>
<li i18n="@@assetSubClass.CASH">Cash</li>
<li i18n="@@assetSubClass.COLLECTIBLE">Collectible</li>
<li i18n="@@assetSubClass.COMMODITY">Commodity</li>
<li i18n="@@assetSubClass.CRYPTOCURRENCY">Cryptocurrency</li>
<li i18n="@@assetSubClass.ETF">ETF</li>
<li i18n="@@assetSubClass.LOAN">Loan</li>
<li i18n="@@assetSubClass.MUTUALFUND">Mutual Fund</li>
<li i18n="@@assetSubClass.PRECIOUS_METAL">Precious Metal</li>
<li i18n="@@assetSubClass.PRIVATE_EQUITY">Private Equity</li>
<li i18n="@@assetSubClass.STOCK">Stock</li>
<li i18n="@@metaDescription">
Ghostfolio is a personal finance dashboard to keep track of your net
worth including cash, stocks, ETFs and cryptocurrencies across multiple

Loading…
Cancel
Save