Browse Source

Merge branch 'main' into less_lodash

pull/4170/head
Thomas Kaul 8 months ago
committed by GitHub
parent
commit
56674ee007
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 22
      CHANGELOG.md
  2. 10
      Dockerfile
  3. 2
      README.md
  4. 2
      apps/api/src/app/app.module.ts
  5. 39
      apps/api/src/app/endpoints/ai/ai.controller.ts
  6. 51
      apps/api/src/app/endpoints/ai/ai.module.ts
  7. 60
      apps/api/src/app/endpoints/ai/ai.service.ts
  8. 27
      apps/api/src/app/info/info.service.ts
  9. 9
      apps/api/src/app/logo/logo.controller.ts
  10. 19
      apps/api/src/app/logo/logo.service.ts
  11. 3
      apps/api/src/app/user/user.service.ts
  12. 211
      apps/api/src/assets/cryptocurrencies/cryptocurrencies.json
  13. 29
      apps/api/src/services/data-provider/coingecko/coingecko.service.ts
  14. 23
      apps/api/src/services/data-provider/data-enhancer/openfigi/openfigi.service.ts
  15. 21
      apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts
  16. 25
      apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts
  17. 20
      apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts
  18. 25
      apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts
  19. 12
      apps/api/src/services/data-provider/manual/manual.service.ts
  20. 8
      apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts
  21. 2
      apps/client/src/app/components/access-table/access-table.component.html
  22. 14
      apps/client/src/app/components/access-table/access-table.component.ts
  23. 19
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  24. 25
      apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
  25. 32
      apps/client/src/app/pages/portfolio/analysis/analysis-page.html
  26. 4
      apps/client/src/app/pages/portfolio/analysis/analysis-page.module.ts
  27. 5
      apps/client/src/app/services/data.service.ts
  28. 52
      apps/client/src/locales/messages.ca.xlf
  29. 52
      apps/client/src/locales/messages.de.xlf
  30. 52
      apps/client/src/locales/messages.es.xlf
  31. 52
      apps/client/src/locales/messages.fr.xlf
  32. 52
      apps/client/src/locales/messages.it.xlf
  33. 52
      apps/client/src/locales/messages.nl.xlf
  34. 52
      apps/client/src/locales/messages.pl.xlf
  35. 52
      apps/client/src/locales/messages.pt.xlf
  36. 52
      apps/client/src/locales/messages.tr.xlf
  37. 50
      apps/client/src/locales/messages.xlf
  38. 52
      apps/client/src/locales/messages.zh.xlf
  39. 2
      libs/common/src/lib/interfaces/index.ts
  40. 3
      libs/common/src/lib/interfaces/responses/ai-prompt-response.interface.ts
  41. 6
      libs/common/src/lib/permissions.ts
  42. 31
      libs/ui/src/lib/fire-calculator/fire-calculator.component.stories.ts
  43. 21
      libs/ui/src/lib/line-chart/line-chart.component.stories.ts
  44. 28
      libs/ui/src/lib/logo/logo.component.stories.ts
  45. 24
      libs/ui/src/lib/no-transactions-info/no-transactions-info.component.stories.ts
  46. 27
      libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.stories.ts
  47. 25
      libs/ui/src/lib/premium-indicator/premium-indicator.component.stories.ts
  48. 53
      libs/ui/src/lib/trend-indicator/trend-indicator.component.stories.ts
  49. 60
      libs/ui/src/lib/value/value.component.stories.ts
  50. 383
      package-lock.json
  51. 11
      package.json
  52. 14
      replace.build.mjs

22
CHANGELOG.md

@ -11,15 +11,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Refactored various `lodash` functions with native JavaScript equivalents
## Unreleased
### Fixed
- Fixed an issue with the renaming of activities with type `FEE`, `INTEREST`, `ITEM` or `LIABILITY`
## 2.133.1 - 2025-01-09
### Added
- Added a _Copy AI prompt to clipboard_ action to the analysis page (experimental)
### Changed
- Improved the usability of the _Copy link to clipboard_ action by adding a confirmation on success in the access table to share the portfolio
- Improved the endpoint to fetch the logo of an asset or a platform by sending the original MIME type
- Eliminated `got` in favor of using `fetch`
- Changed the `REDIS_HOST` from `localhost` to `redis` in `.env.example`
- Changed the _Postgres_ host from `localhost` to `postgres` in `.env.example`
- Changed the _Postgres_ image from `postgres:15` to `postgres:15-alpine` in the `docker-compose` files
- Introduced `extends` in the `docker-compose` files
- Improved the language localization for German (`de`)
- Refreshed the cryptocurrencies list
- Upgraded `envalid` from version `7.3.1` to `8.0.0`
- Upgraded `replace-in-file` from version `7.0.1` to `8.3.0`
### Fixed
- Improved the handling of a missing url in the endpoint to fetch the logo of an asset or a platform
- Fixed the _Storybook_ setup
## 2.132.0 - 2024-12-30
@ -43,6 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Upgraded `ngx-markdown` from version `18.0.0` to `19.0.0`
- Upgraded `Nx` from version `20.1.2` to `20.3.0`
- Upgraded `prisma` from version `6.0.1` to `6.1.0`
- Upgraded `storybook` from version `8.2.5` to `8.4.7`
- Upgraded `zone.js` from version `0.14.10` to `0.15.0`
### Fixed

10
Dockerfile

@ -25,13 +25,13 @@ RUN npm install
COPY ./decorate-angular-cli.js decorate-angular-cli.js
RUN node decorate-angular-cli.js
COPY ./nx.json nx.json
COPY ./replace.build.js replace.build.js
COPY ./jest.preset.js jest.preset.js
COPY ./apps apps
COPY ./libs libs
COPY ./jest.config.ts jest.config.ts
COPY ./jest.preset.js jest.preset.js
COPY ./nx.json nx.json
COPY ./replace.build.mjs replace.build.mjs
COPY ./tsconfig.base.json tsconfig.base.json
COPY ./libs libs
COPY ./apps apps
RUN npm run build:production

2
README.md

@ -306,6 +306,6 @@ If you like to support this project, get [**Ghostfolio Premium**](https://ghostf
## License
© 2021 - 2024 [Ghostfolio](https://ghostfol.io)
© 2021 - 2025 [Ghostfolio](https://ghostfol.io)
Licensed under the [AGPLv3 License](https://www.gnu.org/licenses/agpl-3.0.html).

2
apps/api/src/app/app.module.ts

@ -31,6 +31,7 @@ import { AuthDeviceModule } from './auth-device/auth-device.module';
import { AuthModule } from './auth/auth.module';
import { BenchmarkModule } from './benchmark/benchmark.module';
import { CacheModule } from './cache/cache.module';
import { AiModule } from './endpoints/ai/ai.module';
import { ApiKeysModule } from './endpoints/api-keys/api-keys.module';
import { GhostfolioModule } from './endpoints/data-providers/ghostfolio/ghostfolio.module';
import { MarketDataModule } from './endpoints/market-data/market-data.module';
@ -57,6 +58,7 @@ import { UserModule } from './user/user.module';
AdminModule,
AccessModule,
AccountModule,
AiModule,
ApiKeysModule,
AssetModule,
AuthDeviceModule,

39
apps/api/src/app/endpoints/ai/ai.controller.ts

@ -0,0 +1,39 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import {
DEFAULT_CURRENCY,
DEFAULT_LANGUAGE_CODE
} from '@ghostfolio/common/config';
import { AiPromptResponse } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import { Controller, Get, Inject, UseGuards } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { AiService } from './ai.service';
@Controller('ai')
export class AiController {
public constructor(
private readonly aiService: AiService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@Get('prompt')
@HasPermission(permissions.readAiPrompt)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getPrompt(): Promise<AiPromptResponse> {
const prompt = await this.aiService.getPrompt({
impersonationId: undefined,
languageCode:
this.request.user.Settings.settings.language ?? DEFAULT_LANGUAGE_CODE,
userCurrency:
this.request.user.Settings.settings.baseCurrency ?? DEFAULT_CURRENCY,
userId: this.request.user.id
});
return { prompt };
}
}

51
apps/api/src/app/endpoints/ai/ai.module.ts

@ -0,0 +1,51 @@
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service';
import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { RulesService } from '@ghostfolio/api/app/portfolio/rules.service';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module';
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common';
import { AiController } from './ai.controller';
import { AiService } from './ai.service';
@Module({
controllers: [AiController],
imports: [
ConfigurationModule,
DataProviderModule,
ExchangeRateDataModule,
ImpersonationModule,
MarketDataModule,
OrderModule,
PortfolioSnapshotQueueModule,
PrismaModule,
RedisCacheModule,
SymbolProfileModule,
UserModule
],
providers: [
AccountBalanceService,
AccountService,
AiService,
CurrentRateService,
MarketDataService,
PortfolioCalculatorFactory,
PortfolioService,
RulesService
]
})
export class AiModule {}

60
apps/api/src/app/endpoints/ai/ai.service.ts

@ -0,0 +1,60 @@
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { Injectable } from '@nestjs/common';
@Injectable()
export class AiService {
public constructor(private readonly portfolioService: PortfolioService) {}
public async getPrompt({
impersonationId,
languageCode,
userCurrency,
userId
}: {
impersonationId: string;
languageCode: string;
userCurrency: string;
userId: string;
}) {
const { holdings } = await this.portfolioService.getDetails({
impersonationId,
userId
});
const holdingsTable = [
'| Name | Symbol | Currency | Asset Class | Asset Sub Class | Allocation in Percentage |',
'| --- | --- | --- | --- | --- | --- |',
...Object.values(holdings)
.sort((a, b) => {
return b.allocationInPercentage - a.allocationInPercentage;
})
.map(
({
allocationInPercentage,
assetClass,
assetSubClass,
currency,
name,
symbol
}) => {
return `| ${name} | ${symbol} | ${currency} | ${assetClass} | ${assetSubClass} | ${(allocationInPercentage * 100).toFixed(3)}% |`;
}
)
];
return [
`You are a neutral financial assistant. Please analyze the following investment portfolio (base currency being ${userCurrency}) in simple words.`,
...holdingsTable,
'Structure your answer with these sections:',
'Overview: Briefly summarize the portfolio’s composition and allocation rationale.',
'Risk Assessment: Identify potential risks, including market volatility, concentration, and sectoral imbalances.',
'Advantages: Highlight strengths, focusing on growth potential, diversification, or other benefits.',
'Disadvantages: Point out weaknesses, such as overexposure or lack of defensive assets.',
'Target Group: Discuss who this portfolio might suit (e.g., risk tolerance, investment goals, life stages, and experience levels).',
'Optimization Ideas: Offer ideas to complement the portfolio, ensuring they are constructive and neutral in tone.',
'Conclusion: Provide a concise summary highlighting key insights.',
`Provide your answer in the following language: ${languageCode}.`
].join('\n');
}
}

27
apps/api/src/app/info/info.service.ts

@ -33,7 +33,6 @@ import { Injectable, Logger } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as cheerio from 'cheerio';
import { format, subDays } from 'date-fns';
import got from 'got';
@Injectable()
export class InfoService {
@ -155,16 +154,15 @@ export class InfoService {
private async countDockerHubPulls(): Promise<number> {
try {
const { pull_count } = await got(
`https://hub.docker.com/v2/repositories/ghostfolio/ghostfolio`,
const { pull_count } = (await fetch(
'https://hub.docker.com/v2/repositories/ghostfolio/ghostfolio',
{
headers: { 'User-Agent': 'request' },
// @ts-ignore
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}
).json<any>();
).then((res) => res.json())) as { pull_count: number };
return pull_count;
} catch (error) {
@ -176,18 +174,17 @@ export class InfoService {
private async countGitHubContributors(): Promise<number> {
try {
const { body } = await got('https://github.com/ghostfolio/ghostfolio', {
// @ts-ignore
const body = await fetch('https://github.com/ghostfolio/ghostfolio', {
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
});
}).then((res) => res.text());
const $ = cheerio.load(body);
return extractNumberFromString({
value: $(
`a[href="/ghostfolio/ghostfolio/graphs/contributors"] .Counter`
'a[href="/ghostfolio/ghostfolio/graphs/contributors"] .Counter'
).text()
});
} catch (error) {
@ -199,16 +196,15 @@ export class InfoService {
private async countGitHubStargazers(): Promise<number> {
try {
const { stargazers_count } = await got(
`https://api.github.com/repos/ghostfolio/ghostfolio`,
const { stargazers_count } = (await fetch(
'https://api.github.com/repos/ghostfolio/ghostfolio',
{
headers: { 'User-Agent': 'request' },
// @ts-ignore
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}
).json<any>();
).then((res) => res.json())) as { stargazers_count: number };
return stargazers_count;
} catch (error) {
@ -323,7 +319,7 @@ export class InfoService {
PROPERTY_BETTER_UPTIME_MONITOR_ID
)) as string;
const { data } = await got(
const { data } = await fetch(
`https://uptime.betterstack.com/api/v2/monitors/${monitorId}/sla?from=${format(
subDays(new Date(), 90),
DATE_FORMAT
@ -334,12 +330,11 @@ export class InfoService {
'API_KEY_BETTER_UPTIME'
)}`
},
// @ts-ignore
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}
).json<any>();
).then((res) => res.json());
return data.attributes.availability / 100;
} catch (error) {

9
apps/api/src/app/logo/logo.controller.ts

@ -26,12 +26,13 @@ export class LogoController {
@Res() response: Response
) {
try {
const buffer = await this.logoService.getLogoByDataSourceAndSymbol({
const { buffer, type } =
await this.logoService.getLogoByDataSourceAndSymbol({
dataSource,
symbol
});
response.contentType('image/png');
response.contentType(type);
response.send(buffer);
} catch {
response.status(HttpStatus.NOT_FOUND).send();
@ -44,9 +45,9 @@ export class LogoController {
@Res() response: Response
) {
try {
const buffer = await this.logoService.getLogoByUrl(url);
const { buffer, type } = await this.logoService.getLogoByUrl(url);
response.contentType('image/png');
response.contentType(type);
response.send(buffer);
} catch {
response.status(HttpStatus.NOT_FOUND).send();

19
apps/api/src/app/logo/logo.service.ts

@ -4,7 +4,6 @@ import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { HttpException, Injectable } from '@nestjs/common';
import { DataSource } from '@prisma/client';
import got from 'got';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
@Injectable()
@ -29,7 +28,7 @@ export class LogoService {
{ dataSource, symbol }
]);
if (!assetProfile) {
if (!assetProfile?.url) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
@ -39,20 +38,26 @@ export class LogoService {
return this.getBuffer(assetProfile.url);
}
public async getLogoByUrl(aUrl: string) {
public getLogoByUrl(aUrl: string) {
return this.getBuffer(aUrl);
}
private getBuffer(aUrl: string) {
return got(
private async getBuffer(aUrl: string) {
const blob = await fetch(
`https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${aUrl}&size=64`,
{
headers: { 'User-Agent': 'request' },
// @ts-ignore
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}
).buffer();
).then((res) => res.blob());
return {
buffer: await blob.arrayBuffer().then((arrayBuffer) => {
return Buffer.from(arrayBuffer);
}),
type: blob.type
};
}
}

3
apps/api/src/app/user/user.service.ts

@ -312,7 +312,8 @@ export class UserService {
currentPermissions = without(
currentPermissions,
permissions.accessHoldingsChart,
permissions.createAccess
permissions.createAccess,
permissions.readAiPrompt
);
// Reset benchmark

211
apps/api/src/assets/cryptocurrencies/cryptocurrencies.json

File diff suppressed because it is too large

29
apps/api/src/services/data-provider/coingecko/coingecko.service.ts

@ -26,12 +26,11 @@ import {
SymbolProfile
} from '@prisma/client';
import { format, fromUnixTime, getUnixTime } from 'date-fns';
import got, { Headers } from 'got';
@Injectable()
export class CoinGeckoService implements DataProviderInterface {
private readonly apiUrl: string;
private readonly headers: Headers = {};
private readonly headers: HeadersInit = {};
public constructor(
private readonly configurationService: ConfigurationService
@ -69,19 +68,18 @@ export class CoinGeckoService implements DataProviderInterface {
};
try {
const { name } = await got(`${this.apiUrl}/coins/${symbol}`, {
const { name } = await fetch(`${this.apiUrl}/coins/${symbol}`, {
headers: this.headers,
// @ts-ignore
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}).json<any>();
}).then((res) => res.json());
response.name = name;
} catch (error) {
let message = error;
if (error?.code === 'ABORT_ERR') {
if (error?.name === 'AbortError') {
message = `RequestError: The operation to get the asset profile for ${symbol} was aborted because the request to the data provider took more than ${(
this.configurationService.get('REQUEST_TIMEOUT') / 1000
).toFixed(3)} seconds`;
@ -114,7 +112,7 @@ export class CoinGeckoService implements DataProviderInterface {
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
}> {
try {
const { prices } = await got(
const { prices } = await fetch(
`${
this.apiUrl
}/coins/${symbol}/market_chart/range?vs_currency=${DEFAULT_CURRENCY.toLowerCase()}&from=${getUnixTime(
@ -122,10 +120,9 @@ export class CoinGeckoService implements DataProviderInterface {
)}&to=${getUnixTime(to)}`,
{
headers: this.headers,
// @ts-ignore
signal: AbortSignal.timeout(requestTimeout)
}
).json<any>();
).then((res) => res.json());
const result: {
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
@ -169,16 +166,15 @@ export class CoinGeckoService implements DataProviderInterface {
}
try {
const quotes = await got(
const quotes = await fetch(
`${this.apiUrl}/simple/price?ids=${symbols.join(
','
)}&vs_currencies=${DEFAULT_CURRENCY.toLowerCase()}`,
{
headers: this.headers,
// @ts-ignore
signal: AbortSignal.timeout(requestTimeout)
}
).json<any>();
).then((res) => res.json());
for (const symbol in quotes) {
response[symbol] = {
@ -192,7 +188,7 @@ export class CoinGeckoService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error?.code === 'ABORT_ERR') {
if (error?.name === 'AbortError') {
message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${(
this.configurationService.get('REQUEST_TIMEOUT') / 1000
).toFixed(3)} seconds`;
@ -212,13 +208,12 @@ export class CoinGeckoService implements DataProviderInterface {
let items: LookupItem[] = [];
try {
const { coins } = await got(`${this.apiUrl}/search?query=${query}`, {
const { coins } = await fetch(`${this.apiUrl}/search?query=${query}`, {
headers: this.headers,
// @ts-ignore
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}).json<any>();
}).then((res) => res.json());
items = coins.map(({ id: symbol, name }) => {
return {
@ -234,7 +229,7 @@ export class CoinGeckoService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error?.code === 'ABORT_ERR') {
if (error?.name === 'AbortError') {
message = `RequestError: The operation to search for ${query} was aborted because the request to the data provider took more than ${(
this.configurationService.get('REQUEST_TIMEOUT') / 1000
).toFixed(3)} seconds`;

23
apps/api/src/services/data-provider/data-enhancer/openfigi/openfigi.service.ts

@ -4,7 +4,6 @@ import { parseSymbol } from '@ghostfolio/common/helper';
import { Injectable } from '@nestjs/common';
import { SymbolProfile } from '@prisma/client';
import got, { Headers } from 'got';
@Injectable()
export class OpenFigiDataEnhancerService implements DataEnhancerInterface {
@ -32,7 +31,7 @@ export class OpenFigiDataEnhancerService implements DataEnhancerInterface {
return response;
}
const headers: Headers = {};
const headers: HeadersInit = {};
const { exchange, ticker } = parseSymbol({
symbol,
dataSource: response.dataSource
@ -43,14 +42,20 @@ export class OpenFigiDataEnhancerService implements DataEnhancerInterface {
this.configurationService.get('API_KEY_OPEN_FIGI');
}
const mappings = await got
.post(`${OpenFigiDataEnhancerService.baseUrl}/v3/mapping`, {
headers,
json: [{ exchCode: exchange, idType: 'TICKER', idValue: ticker }],
// @ts-ignore
const mappings = (await fetch(
`${OpenFigiDataEnhancerService.baseUrl}/v3/mapping`,
{
body: JSON.stringify([
{ exchCode: exchange, idType: 'TICKER', idValue: ticker }
]),
headers: {
'Content-Type': 'application/json',
...headers
},
method: 'POST',
signal: AbortSignal.timeout(requestTimeout)
})
.json<any[]>();
}
).then((res) => res.json())) as any[];
if (mappings?.length === 1 && mappings[0].data?.length === 1) {
const { compositeFIGI, figi, shareClassFIGI } = mappings[0].data[0];

21
apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts

@ -7,7 +7,6 @@ import { Sector } from '@ghostfolio/common/interfaces/sector.interface';
import { Injectable } from '@nestjs/common';
import { SymbolProfile } from '@prisma/client';
import { countries } from 'countries-list';
import got from 'got';
@Injectable()
export class TrackinsightDataEnhancerService implements DataEnhancerInterface {
@ -45,27 +44,25 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface {
return response;
}
const profile = await got(
const profile = await fetch(
`${TrackinsightDataEnhancerService.baseUrl}/funds/${symbol}.json`,
{
// @ts-ignore
signal: AbortSignal.timeout(requestTimeout)
}
)
.json<any>()
.then((res) => res.json())
.catch(() => {
return got(
return fetch(
`${TrackinsightDataEnhancerService.baseUrl}/funds/${
symbol.split('.')?.[0]
}.json`,
{
// @ts-ignore
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}
)
.json<any>()
.then((res) => res.json())
.catch(() => {
return {};
});
@ -77,29 +74,27 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface {
response.isin = isin;
}
const holdings = await got(
const holdings = await fetch(
`${TrackinsightDataEnhancerService.baseUrl}/holdings/${symbol}.json`,
{
// @ts-ignore
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}
)
.json<any>()
.then((res) => res.json())
.catch(() => {
return got(
return fetch(
`${TrackinsightDataEnhancerService.baseUrl}/holdings/${
symbol.split('.')?.[0]
}.json`,
{
// @ts-ignore
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}
)
.json<any>()
.then((res) => res.json())
.catch(() => {
return {};
});

25
apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts

@ -31,7 +31,6 @@ import {
SymbolProfile
} from '@prisma/client';
import { addDays, format, isSameDay, isToday } from 'date-fns';
import got from 'got';
import { isNumber } from 'lodash';
@Injectable()
@ -95,7 +94,7 @@ export class EodHistoricalDataService implements DataProviderInterface {
[date: string]: IDataProviderHistoricalResponse;
} = {};
const historicalResult = await got(
const historicalResult = await fetch(
`${this.URL}/div/${symbol}?api_token=${
this.apiKey
}&fmt=json&from=${format(from, DATE_FORMAT)}&to=${format(
@ -103,10 +102,9 @@ export class EodHistoricalDataService implements DataProviderInterface {
DATE_FORMAT
)}`,
{
// @ts-ignore
signal: AbortSignal.timeout(requestTimeout)
}
).json<any>();
).then((res) => res.json());
for (const { date, value } of historicalResult) {
response[date] = {
@ -140,7 +138,7 @@ export class EodHistoricalDataService implements DataProviderInterface {
symbol = this.convertToEodSymbol(symbol);
try {
const response = await got(
const response = await fetch(
`${this.URL}/eod/${symbol}?api_token=${
this.apiKey
}&fmt=json&from=${format(from, DATE_FORMAT)}&to=${format(
@ -148,10 +146,9 @@ export class EodHistoricalDataService implements DataProviderInterface {
DATE_FORMAT
)}&period=${granularity}`,
{
// @ts-ignore
signal: AbortSignal.timeout(requestTimeout)
}
).json<any>();
).then((res) => res.json());
return response.reduce(
(result, { adjusted_close, date }) => {
@ -205,15 +202,14 @@ export class EodHistoricalDataService implements DataProviderInterface {
});
try {
const realTimeResponse = await got(
const realTimeResponse = await fetch(
`${this.URL}/real-time/${eodHistoricalDataSymbols[0]}?api_token=${
this.apiKey
}&fmt=json&s=${eodHistoricalDataSymbols.join(',')}`,
{
// @ts-ignore
signal: AbortSignal.timeout(requestTimeout)
}
).json<any>();
).then((res) => res.json());
const quotes: {
close: number;
@ -286,7 +282,7 @@ export class EodHistoricalDataService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error?.code === 'ABORT_ERR') {
if (error?.name === 'AbortError') {
message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${(
this.configurationService.get('REQUEST_TIMEOUT') / 1000
).toFixed(3)} seconds`;
@ -400,15 +396,14 @@ export class EodHistoricalDataService implements DataProviderInterface {
})[] = [];
try {
const response = await got(
const response = await fetch(
`${this.URL}/search/${aQuery}?api_token=${this.apiKey}`,
{
// @ts-ignore
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}
).json<any>();
).then((res) => res.json());
searchResult = response.map(
({ Code, Currency, Exchange, ISIN: isin, Name: name, Type }) => {
@ -431,7 +426,7 @@ export class EodHistoricalDataService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error?.code === 'ABORT_ERR') {
if (error?.name === 'AbortError') {
message = `RequestError: The operation to search for ${aQuery} was aborted because the request to the data provider took more than ${(
this.configurationService.get('REQUEST_TIMEOUT') / 1000
).toFixed(3)} seconds`;

20
apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts

@ -21,7 +21,6 @@ import {
import { Injectable, Logger } from '@nestjs/common';
import { DataSource, SymbolProfile } from '@prisma/client';
import { format, isAfter, isBefore, isSameDay } from 'date-fns';
import got from 'got';
@Injectable()
export class FinancialModelingPrepService implements DataProviderInterface {
@ -72,13 +71,12 @@ export class FinancialModelingPrepService implements DataProviderInterface {
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
}> {
try {
const { historical } = await got(
const { historical } = await fetch(
`${this.URL}/historical-price-full/${symbol}?apikey=${this.apiKey}`,
{
// @ts-ignore
signal: AbortSignal.timeout(requestTimeout)
}
).json<any>();
).then((res) => res.json());
const result: {
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
@ -124,13 +122,12 @@ export class FinancialModelingPrepService implements DataProviderInterface {
}
try {
const quotes = await got(
const quotes = await fetch(
`${this.URL}/quote/${symbols.join(',')}?apikey=${this.apiKey}`,
{
// @ts-ignore
signal: AbortSignal.timeout(requestTimeout)
}
).json<any>();
).then((res) => res.json());
for (const { price, symbol } of quotes) {
response[symbol] = {
@ -144,7 +141,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error?.code === 'ABORT_ERR') {
if (error?.name === 'AbortError') {
message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${(
this.configurationService.get('REQUEST_TIMEOUT') / 1000
).toFixed(3)} seconds`;
@ -164,15 +161,14 @@ export class FinancialModelingPrepService implements DataProviderInterface {
let items: LookupItem[] = [];
try {
const result = await got(
const result = await fetch(
`${this.URL}/search?query=${query}&apikey=${this.apiKey}`,
{
// @ts-ignore
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}
).json<any>();
).then((res) => res.json());
items = result.map(({ currency, name, symbol }) => {
return {
@ -187,7 +183,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error?.code === 'ABORT_ERR') {
if (error?.name === 'AbortError') {
message = `RequestError: The operation to search for ${query} was aborted because the request to the data provider took more than ${(
this.configurationService.get('REQUEST_TIMEOUT') / 1000
).toFixed(3)} seconds`;

25
apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts

@ -28,7 +28,6 @@ import {
import { Injectable, Logger } from '@nestjs/common';
import { DataSource, SymbolProfile } from '@prisma/client';
import { format } from 'date-fns';
import got from 'got';
import { StatusCodes } from 'http-status-codes';
@Injectable()
@ -86,17 +85,16 @@ export class GhostfolioService implements DataProviderInterface {
} = {};
try {
const { dividends } = await got(
const { dividends } = (await fetch(
`${this.URL}/v2/data-providers/ghostfolio/dividends/${symbol}?from=${format(from, DATE_FORMAT)}&granularity=${granularity}&to=${format(
to,
DATE_FORMAT
)}`,
{
headers: await this.getRequestHeaders(),
// @ts-ignore
signal: AbortSignal.timeout(requestTimeout)
}
).json<DividendsResponse>();
).then((res) => res.json())) as DividendsResponse;
response = dividends;
} catch (error) {
@ -130,17 +128,16 @@ export class GhostfolioService implements DataProviderInterface {
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
}> {
try {
const { historicalData } = await got(
const { historicalData } = (await fetch(
`${this.URL}/v2/data-providers/ghostfolio/historical/${symbol}?from=${format(from, DATE_FORMAT)}&granularity=${granularity}&to=${format(
to,
DATE_FORMAT
)}`,
{
headers: await this.getRequestHeaders(),
// @ts-ignore
signal: AbortSignal.timeout(requestTimeout)
}
).json<HistoricalResponse>();
).then((res) => res.json())) as HistoricalResponse;
return {
[symbol]: historicalData
@ -192,20 +189,19 @@ export class GhostfolioService implements DataProviderInterface {
}
try {
const { quotes } = await got(
const { quotes } = (await fetch(
`${this.URL}/v2/data-providers/ghostfolio/quotes?symbols=${symbols.join(',')}`,
{
headers: await this.getRequestHeaders(),
// @ts-ignore
signal: AbortSignal.timeout(requestTimeout)
}
).json<QuotesResponse>();
).then((res) => res.json())) as QuotesResponse;
response = quotes;
} catch (error) {
let message = error;
if (error.code === 'ABORT_ERR') {
if (error.name === 'AbortError') {
message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${(
this.configurationService.get('REQUEST_TIMEOUT') / 1000
).toFixed(3)} seconds`;
@ -235,20 +231,19 @@ export class GhostfolioService implements DataProviderInterface {
let searchResult: LookupResponse = { items: [] };
try {
searchResult = await got(
searchResult = (await fetch(
`${this.URL}/v2/data-providers/ghostfolio/lookup?query=${query}`,
{
headers: await this.getRequestHeaders(),
// @ts-ignore
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}
).json<LookupResponse>();
).then((res) => res.json())) as LookupResponse;
} catch (error) {
let message = error;
if (error.code === 'ABORT_ERR') {
if (error.name === 'AbortError') {
message = `RequestError: The operation to search for ${query} was aborted because the request to the data provider took more than ${(
this.configurationService.get('REQUEST_TIMEOUT') / 1000
).toFixed(3)} seconds`;

12
apps/api/src/services/data-provider/manual/manual.service.ts

@ -27,7 +27,6 @@ import { Injectable, Logger } from '@nestjs/common';
import { DataSource, SymbolProfile } from '@prisma/client';
import * as cheerio from 'cheerio';
import { addDays, format, isBefore } from 'date-fns';
import got, { Headers } from 'got';
import * as jsonpath from 'jsonpath';
@Injectable()
@ -276,23 +275,22 @@ export class ManualService implements DataProviderInterface {
): Promise<number> {
try {
let locale = scraperConfiguration.locale;
const { body, headers } = await got(scraperConfiguration.url, {
headers: scraperConfiguration.headers as Headers,
// @ts-ignore
const response = await fetch(scraperConfiguration.url, {
headers: scraperConfiguration.headers as HeadersInit,
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
});
if (headers['content-type'].includes('application/json')) {
const data = JSON.parse(body);
if (response.headers['content-type'].includes('application/json')) {
const data = await response.json();
const value = String(
jsonpath.query(data, scraperConfiguration.selector)[0]
);
return extractNumberFromString({ locale, value });
} else {
const $ = cheerio.load(body);
const $ = cheerio.load(await response.text());
if (!locale) {
try {

8
apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts

@ -20,7 +20,6 @@ import {
import { Injectable, Logger } from '@nestjs/common';
import { DataSource, SymbolProfile } from '@prisma/client';
import { format } from 'date-fns';
import got from 'got';
@Injectable()
export class RapidApiService implements DataProviderInterface {
@ -135,7 +134,7 @@ export class RapidApiService implements DataProviderInterface {
oneYearAgo: { value: number; valueText: string };
}> {
try {
const { fgi } = await got(
const { fgi } = await fetch(
`https://fear-and-greed-index.p.rapidapi.com/v1/fgi`,
{
headers: {
@ -143,18 +142,17 @@ export class RapidApiService implements DataProviderInterface {
'x-rapidapi-host': 'fear-and-greed-index.p.rapidapi.com',
'x-rapidapi-key': this.configurationService.get('API_KEY_RAPID_API')
},
// @ts-ignore
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}
).json<any>();
).then((res) => res.json());
return fgi;
} catch (error) {
let message = error;
if (error?.code === 'ABORT_ERR') {
if (error?.name === 'AbortError') {
message = `RequestError: The operation was aborted because the request to the data provider took more than ${(
this.configurationService.get('REQUEST_TIMEOUT') / 1000
).toFixed(3)} seconds`;

2
apps/client/src/app/components/access-table/access-table.component.html

@ -66,7 +66,7 @@
</button>
<mat-menu #transactionMenu="matMenu" xPosition="before">
@if (element.type === 'PUBLIC') {
<button mat-menu-item (click)="onCopyToClipboard(element.id)">
<button mat-menu-item (click)="onCopyUrlToClipboard(element.id)">
<ng-container i18n>Copy link to clipboard</ng-container>
</button>
<hr class="my-0" />

14
apps/client/src/app/components/access-table/access-table.component.ts

@ -12,6 +12,7 @@ import {
OnChanges,
Output
} from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MatTableDataSource } from '@angular/material/table';
@Component({
@ -34,7 +35,8 @@ export class AccessTableComponent implements OnChanges {
public constructor(
private clipboard: Clipboard,
private notificationService: NotificationService
private notificationService: NotificationService,
private snackBar: MatSnackBar
) {}
public ngOnChanges() {
@ -55,8 +57,16 @@ export class AccessTableComponent implements OnChanges {
return `${this.baseUrl}/${languageCode}/p/${aId}`;
}
public onCopyToClipboard(aId: string): void {
public onCopyUrlToClipboard(aId: string): void {
this.clipboard.copy(this.getPublicUrl(aId));
this.snackBar.open(
'✅ ' + $localize`Link has been copied to the clipboard`,
undefined,
{
duration: 3000
}
);
}
public onDeleteAccess(aId: string) {

19
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts

@ -1,7 +1,5 @@
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
import { DataService } from '@ghostfolio/client/services/data.service';
import { validateObjectForForm } from '@ghostfolio/client/util/form.util';
import { getDateFormatString } from '@ghostfolio/common/helper';
import { translate } from '@ghostfolio/ui/i18n';
@ -24,6 +22,8 @@ import { isAfter, isToday } from 'date-fns';
import { EMPTY, Observable, Subject, lastValueFrom, of } from 'rxjs';
import { catchError, delay, map, startWith, takeUntil } from 'rxjs/operators';
import { DataService } from '../../../../services/data.service';
import { validateObjectForForm } from '../../../../util/form.util';
import { CreateOrUpdateActivityDialogParams } from './interfaces/interfaces';
@Component({
@ -124,7 +124,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
name: [this.data.activity?.SymbolProfile?.name, Validators.required],
quantity: [this.data.activity?.quantity, Validators.required],
searchSymbol: [
!!this.data.activity?.SymbolProfile
this.data.activity?.SymbolProfile
? {
dataSource: this.data.activity?.SymbolProfile?.dataSource,
symbol: this.data.activity?.SymbolProfile?.symbol
@ -476,7 +476,11 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
fee: this.activityForm.get('fee').value,
quantity: this.activityForm.get('quantity').value,
symbol:
this.activityForm.get('searchSymbol')?.value?.symbol ??
(['FEE', 'INTEREST', 'ITEM', 'LIABILITY'].includes(
this.activityForm.get('type').value
)
? undefined
: this.activityForm.get('searchSymbol')?.value?.symbol) ??
this.activityForm.get('name')?.value,
tags: this.activityForm.get('tags').value,
type: this.activityForm.get('type').value,
@ -485,8 +489,9 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
try {
if (this.mode === 'create') {
(activity as CreateOrderDto).updateAccountBalance =
this.activityForm.get('updateAccountBalance').value;
activity.updateAccountBalance = this.activityForm.get(
'updateAccountBalance'
).value;
await validateObjectForForm({
classDto: CreateOrderDto,
@ -495,7 +500,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
object: activity
});
this.dialogRef.close(activity as CreateOrderDto);
this.dialogRef.close(activity);
} else {
(activity as UpdateOrderDto).id = this.data.activity.id;

25
apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts

@ -11,10 +11,13 @@ import {
ToggleOption,
User
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { GroupBy } from '@ghostfolio/common/types';
import { translate } from '@ghostfolio/ui/i18n';
import { Clipboard } from '@angular/cdk/clipboard';
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { SymbolProfile } from '@prisma/client';
import { isNumber, sortBy } from 'lodash';
import { DeviceDetectorService } from 'ngx-device-detector';
@ -38,6 +41,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
public dividendTimelineDataLabel = $localize`Dividend`;
public firstOrderDate: Date;
public hasImpersonationId: boolean;
public hasPermissionToReadAiPrompt: boolean;
public investments: InvestmentItem[];
public investmentTimelineDataLabel = $localize`Investment`;
public investmentsByGroup: InvestmentItem[];
@ -64,9 +68,11 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private clipboard: Clipboard,
private dataService: DataService,
private deviceService: DeviceDetectorService,
private impersonationStorageService: ImpersonationStorageService,
private snackBar: MatSnackBar,
private userService: UserService
) {
const { benchmarks } = this.dataService.fetchInfo();
@ -104,6 +110,11 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
return id === this.user.settings?.benchmark;
});
this.hasPermissionToReadAiPrompt = hasPermission(
this.user.permissions,
permissions.readAiPrompt
);
this.update();
}
});
@ -130,6 +141,20 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
this.fetchDividendsAndInvestments();
}
public onCopyPromptToClipboard() {
this.dataService.fetchPrompt().subscribe(({ prompt }) => {
this.clipboard.copy(prompt);
this.snackBar.open(
'✅ ' + $localize`AI prompt has been copied to the clipboard`,
undefined,
{
duration: 3000
}
);
});
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();

32
apps/client/src/app/pages/portfolio/analysis/analysis-page.html

@ -1,5 +1,37 @@
<div class="container">
<h1 class="d-none d-sm-block h3 mb-3 text-center" i18n>Analysis</h1>
@if (user?.settings?.isExperimentalFeatures) {
<div class="mb-3 row">
<div class="col-lg">
<div class="d-flex justify-content-end">
<button
class="mx-1 no-min-width px-2"
mat-stroked-button
[matMenuTriggerFor]="actionsMenu"
(click)="$event.stopPropagation()"
>
<ion-icon name="ellipsis-vertical" />
</button>
<mat-menu #actionsMenu="matMenu" xPosition="before">
<button
mat-menu-item
[disabled]="!hasPermissionToReadAiPrompt"
(click)="onCopyPromptToClipboard()"
>
<span class="align-items-center d-flex">
@if (user?.subscription?.type === 'Basic') {
<gf-premium-indicator class="mr-2" />
} @else {
<ion-icon class="mr-2" name="copy-outline" />
}
<ng-container i18n>Copy AI prompt to clipboard</ng-container>
</span>
</button>
</mat-menu>
</div>
</div>
</div>
}
<div class="mb-5 row">
<div class="col-lg">
<gf-benchmark-comparator

4
apps/client/src/app/pages/portfolio/analysis/analysis-page.module.ts

@ -7,7 +7,9 @@ import { GfValueComponent } from '@ghostfolio/ui/value';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatMenuModule } from '@angular/material/menu';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { AnalysisPageRoutingModule } from './analysis-page-routing.module';
@ -24,7 +26,9 @@ import { AnalysisPageComponent } from './analysis-page.component';
GfPremiumIndicatorComponent,
GfToggleModule,
GfValueComponent,
MatButtonModule,
MatCardModule,
MatMenuModule,
NgxSkeletonLoaderModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]

5
apps/client/src/app/services/data.service.ts

@ -22,6 +22,7 @@ import {
Access,
AccountBalancesResponse,
Accounts,
AiPromptResponse,
ApiKeyResponse,
AssetProfileIdentifier,
BenchmarkMarketDataDetails,
@ -637,6 +638,10 @@ export class DataService {
return this.http.get<PortfolioReportResponse>('/api/v1/portfolio/report');
}
public fetchPrompt() {
return this.http.get<AiPromptResponse>('/api/v1/ai/prompt');
}
public fetchPublicPortfolio(aAccessId: string) {
return this.http
.get<PublicPortfolioResponse>(`/api/v1/public/${aAccessId}/portfolio`)

52
apps/client/src/locales/messages.ca.xlf

@ -5067,7 +5067,7 @@
<target state="new">Dividend</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">38</context>
<context context-type="linenumber">41</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -5079,11 +5079,11 @@
<target state="new">Investment</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">42</context>
<context context-type="linenumber">46</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">56</context>
<context context-type="linenumber">60</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts</context>
@ -5095,7 +5095,7 @@
<target state="new">Monthly</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">50</context>
<context context-type="linenumber">54</context>
</context-group>
</trans-unit>
<trans-unit id="8036977202721714375" datatype="html">
@ -5103,7 +5103,7 @@
<target state="new">Yearly</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">51</context>
<context context-type="linenumber">55</context>
</context-group>
</trans-unit>
<trans-unit id="f6413089f93ac0756a20f8e74ef1ab78c81ef13c" datatype="html">
@ -5119,7 +5119,7 @@
<target state="new">Absolute Asset Performance</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">28</context>
<context context-type="linenumber">60</context>
</context-group>
</trans-unit>
<trans-unit id="c726a56ba67c6c788e3759983dd8a1671d8cc886" datatype="html">
@ -5127,7 +5127,7 @@
<target state="new"> Asset Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">49</context>
<context context-type="linenumber">81</context>
</context-group>
</trans-unit>
<trans-unit id="8ce52b52483f502dd23ed290357a17307c60280c" datatype="html">
@ -5135,7 +5135,7 @@
<target state="new">Absolute Currency Performance</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">71</context>
<context context-type="linenumber">103</context>
</context-group>
</trans-unit>
<trans-unit id="e4da628796a8880899b986c2af0559a55d6a700c" datatype="html">
@ -5143,7 +5143,7 @@
<target state="new"> Currency Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">95</context>
<context context-type="linenumber">127</context>
</context-group>
</trans-unit>
<trans-unit id="5584854134b3049db7dfb7bf4d87a0b9b9b4b149" datatype="html">
@ -5151,7 +5151,7 @@
<target state="new"> Absolute Net Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">118</context>
<context context-type="linenumber">150</context>
</context-group>
</trans-unit>
<trans-unit id="d88d656d93dd2029b9d35712789d2567d2c0d739" datatype="html">
@ -5159,7 +5159,7 @@
<target state="new"> Net Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">137</context>
<context context-type="linenumber">169</context>
</context-group>
</trans-unit>
<trans-unit id="6ae1c94f6bad274424f97e9bc8766242c1577447" datatype="html">
@ -5167,7 +5167,7 @@
<target state="new">Top</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">165</context>
<context context-type="linenumber">197</context>
</context-group>
</trans-unit>
<trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
@ -5175,7 +5175,7 @@
<target state="new">Bottom</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">214</context>
<context context-type="linenumber">246</context>
</context-group>
</trans-unit>
<trans-unit id="f1a355a1af2e818050a3af693ac8b521fa7edc5f" datatype="html">
@ -5183,7 +5183,7 @@
<target state="new">Portfolio Evolution</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">267</context>
<context context-type="linenumber">299</context>
</context-group>
</trans-unit>
<trans-unit id="658bfe96ba9d3a3a2ada478c8c855286b841f92a" datatype="html">
@ -5191,7 +5191,7 @@
<target state="new">Investment Timeline</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">294</context>
<context context-type="linenumber">326</context>
</context-group>
</trans-unit>
<trans-unit id="1c275927e7e22395d21a86e4ab459e428bcac27e" datatype="html">
@ -5199,7 +5199,7 @@
<target state="new">Current Streak</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">315</context>
<context context-type="linenumber">347</context>
</context-group>
</trans-unit>
<trans-unit id="eabb7b2ede5498042bc9fbb565981a780bf340dc" datatype="html">
@ -5207,7 +5207,7 @@
<target state="new">Longest Streak</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">324</context>
<context context-type="linenumber">356</context>
</context-group>
</trans-unit>
<trans-unit id="6410cffb96159fcff46d91effc26df0e240bc0e3" datatype="html">
@ -5215,7 +5215,7 @@
<target state="new">Dividend Timeline</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">351</context>
<context context-type="linenumber">383</context>
</context-group>
</trans-unit>
<trans-unit id="6382f0e408575450219d3a10f8883a8f6c67e7d7" datatype="html">
@ -7636,6 +7636,22 @@
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit id="7826234236931647519" datatype="html">
<source>AI prompt has been copied to the clipboard</source>
<target state="new">AI prompt has been copied to the clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">149</context>
</context-group>
</trans-unit>
<trans-unit id="4eecb3acb016e7d86740405b21599cdad8e0dd9f" datatype="html">
<source>Copy AI prompt to clipboard</source>
<target state="new">Copy AI prompt to clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

52
apps/client/src/locales/messages.de.xlf

@ -2146,7 +2146,7 @@
<target state="translated">Zeitstrahl der Investitionen</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">294</context>
<context context-type="linenumber">326</context>
</context-group>
</trans-unit>
<trans-unit id="6ae1c94f6bad274424f97e9bc8766242c1577447" datatype="html">
@ -2154,7 +2154,7 @@
<target state="translated">Gewinner</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">165</context>
<context context-type="linenumber">197</context>
</context-group>
</trans-unit>
<trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
@ -2162,7 +2162,7 @@
<target state="translated">Verlierer</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">214</context>
<context context-type="linenumber">246</context>
</context-group>
</trans-unit>
<trans-unit id="6382f0e408575450219d3a10f8883a8f6c67e7d7" datatype="html">
@ -2806,7 +2806,7 @@
<target state="translated">Monatlich</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">50</context>
<context context-type="linenumber">54</context>
</context-group>
</trans-unit>
<trans-unit id="5213771062241898526" datatype="html">
@ -2998,7 +2998,7 @@
<target state="translated">Portfolio Wertentwicklung</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">267</context>
<context context-type="linenumber">299</context>
</context-group>
</trans-unit>
<trans-unit id="8192718423057883427" datatype="html">
@ -3314,7 +3314,7 @@
<target state="translated">Zeitstrahl der Dividenden</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">351</context>
<context context-type="linenumber">383</context>
</context-group>
</trans-unit>
<trans-unit id="7765499580020598783" datatype="html">
@ -3322,7 +3322,7 @@
<target state="translated">Dividenden</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">38</context>
<context context-type="linenumber">41</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -3430,7 +3430,7 @@
<target state="translated">Jährlich</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">51</context>
<context context-type="linenumber">55</context>
</context-group>
</trans-unit>
<trans-unit id="72640258012696878" datatype="html">
@ -4174,7 +4174,7 @@
<target state="translated">Aktueller Streak</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">315</context>
<context context-type="linenumber">347</context>
</context-group>
</trans-unit>
<trans-unit id="eabb7b2ede5498042bc9fbb565981a780bf340dc" datatype="html">
@ -4182,7 +4182,7 @@
<target state="translated">Längster Streak</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">324</context>
<context context-type="linenumber">356</context>
</context-group>
</trans-unit>
<trans-unit id="4845030128243887325" datatype="html">
@ -6311,11 +6311,11 @@
<target state="translated">Einlage</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">42</context>
<context context-type="linenumber">46</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">56</context>
<context context-type="linenumber">60</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts</context>
@ -6327,7 +6327,7 @@
<target state="translated"> Absolute Anlage Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">28</context>
<context context-type="linenumber">60</context>
</context-group>
</trans-unit>
<trans-unit id="c726a56ba67c6c788e3759983dd8a1671d8cc886" datatype="html">
@ -6335,7 +6335,7 @@
<target state="translated"> Anlage Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">49</context>
<context context-type="linenumber">81</context>
</context-group>
</trans-unit>
<trans-unit id="8ce52b52483f502dd23ed290357a17307c60280c" datatype="html">
@ -6343,7 +6343,7 @@
<target state="translated"> Absolute Währungsperformance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">71</context>
<context context-type="linenumber">103</context>
</context-group>
</trans-unit>
<trans-unit id="e4da628796a8880899b986c2af0559a55d6a700c" datatype="html">
@ -6351,7 +6351,7 @@
<target state="translated"> Währungsperformance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">95</context>
<context context-type="linenumber">127</context>
</context-group>
</trans-unit>
<trans-unit id="5584854134b3049db7dfb7bf4d87a0b9b9b4b149" datatype="html">
@ -6359,7 +6359,7 @@
<target state="translated"> Absolute Netto Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">118</context>
<context context-type="linenumber">150</context>
</context-group>
</trans-unit>
<trans-unit id="d88d656d93dd2029b9d35712789d2567d2c0d739" datatype="html">
@ -6367,7 +6367,7 @@
<target state="translated"> Netto Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">137</context>
<context context-type="linenumber">169</context>
</context-group>
</trans-unit>
<trans-unit id="3105754554141014845" datatype="html">
@ -7636,6 +7636,22 @@
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit id="7826234236931647519" datatype="html">
<source>AI prompt has been copied to the clipboard</source>
<target state="translated">KI-Anweisung wurde in die Zwischenablage kopiert</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">149</context>
</context-group>
</trans-unit>
<trans-unit id="4eecb3acb016e7d86740405b21599cdad8e0dd9f" datatype="html">
<source>Copy AI prompt to clipboard</source>
<target state="translated">Kopiere KI-Anweisung die Zwischenablage</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

52
apps/client/src/locales/messages.es.xlf

@ -2147,7 +2147,7 @@
<target state="translated">Cronología de la inversión</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">294</context>
<context context-type="linenumber">326</context>
</context-group>
</trans-unit>
<trans-unit id="6ae1c94f6bad274424f97e9bc8766242c1577447" datatype="html">
@ -2155,7 +2155,7 @@
<target state="translated">Lo mejor</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">165</context>
<context context-type="linenumber">197</context>
</context-group>
</trans-unit>
<trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
@ -2163,7 +2163,7 @@
<target state="translated">Lo peor</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">214</context>
<context context-type="linenumber">246</context>
</context-group>
</trans-unit>
<trans-unit id="6382f0e408575450219d3a10f8883a8f6c67e7d7" datatype="html">
@ -2835,7 +2835,7 @@
<target state="translated">Mensual</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">50</context>
<context context-type="linenumber">54</context>
</context-group>
</trans-unit>
<trans-unit id="8511b16abcf065252b350d64e337ba2447db3ffb" datatype="html">
@ -2999,7 +2999,7 @@
<target state="translated">Evolución cartera</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">267</context>
<context context-type="linenumber">299</context>
</context-group>
</trans-unit>
<trans-unit id="8192718423057883427" datatype="html">
@ -3315,7 +3315,7 @@
<target state="translated">Dividendo</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">38</context>
<context context-type="linenumber">41</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -3327,7 +3327,7 @@
<target state="translated">Calendario de dividendos</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">351</context>
<context context-type="linenumber">383</context>
</context-group>
</trans-unit>
<trans-unit id="7608037008789240367" datatype="html">
@ -3431,7 +3431,7 @@
<target state="translated">Anual</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">51</context>
<context context-type="linenumber">55</context>
</context-group>
</trans-unit>
<trans-unit id="72640258012696878" datatype="html">
@ -4175,7 +4175,7 @@
<target state="new">Current Streak</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">315</context>
<context context-type="linenumber">347</context>
</context-group>
</trans-unit>
<trans-unit id="eabb7b2ede5498042bc9fbb565981a780bf340dc" datatype="html">
@ -4183,7 +4183,7 @@
<target state="new">Longest Streak</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">324</context>
<context context-type="linenumber">356</context>
</context-group>
</trans-unit>
<trans-unit id="4845030128243887325" datatype="html">
@ -6312,11 +6312,11 @@
<target state="translated">Inversión</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">42</context>
<context context-type="linenumber">46</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">56</context>
<context context-type="linenumber">60</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts</context>
@ -6328,7 +6328,7 @@
<target state="new">Absolute Asset Performance</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">28</context>
<context context-type="linenumber">60</context>
</context-group>
</trans-unit>
<trans-unit id="c726a56ba67c6c788e3759983dd8a1671d8cc886" datatype="html">
@ -6336,7 +6336,7 @@
<target state="new"> Asset Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">49</context>
<context context-type="linenumber">81</context>
</context-group>
</trans-unit>
<trans-unit id="8ce52b52483f502dd23ed290357a17307c60280c" datatype="html">
@ -6344,7 +6344,7 @@
<target state="new">Absolute Currency Performance</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">71</context>
<context context-type="linenumber">103</context>
</context-group>
</trans-unit>
<trans-unit id="e4da628796a8880899b986c2af0559a55d6a700c" datatype="html">
@ -6352,7 +6352,7 @@
<target state="new"> Currency Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">95</context>
<context context-type="linenumber">127</context>
</context-group>
</trans-unit>
<trans-unit id="5584854134b3049db7dfb7bf4d87a0b9b9b4b149" datatype="html">
@ -6360,7 +6360,7 @@
<target state="new"> Absolute Net Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">118</context>
<context context-type="linenumber">150</context>
</context-group>
</trans-unit>
<trans-unit id="d88d656d93dd2029b9d35712789d2567d2c0d739" datatype="html">
@ -6368,7 +6368,7 @@
<target state="new"> Net Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">137</context>
<context context-type="linenumber">169</context>
</context-group>
</trans-unit>
<trans-unit id="3105754554141014845" datatype="html">
@ -7637,6 +7637,22 @@
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit id="7826234236931647519" datatype="html">
<source>AI prompt has been copied to the clipboard</source>
<target state="new">AI prompt has been copied to the clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">149</context>
</context-group>
</trans-unit>
<trans-unit id="4eecb3acb016e7d86740405b21599cdad8e0dd9f" datatype="html">
<source>Copy AI prompt to clipboard</source>
<target state="new">Copy AI prompt to clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

52
apps/client/src/locales/messages.fr.xlf

@ -2746,7 +2746,7 @@
<target state="translated">Dividende</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">38</context>
<context context-type="linenumber">41</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -2766,7 +2766,7 @@
<target state="translated">Mensuel</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">50</context>
<context context-type="linenumber">54</context>
</context-group>
</trans-unit>
<trans-unit id="f6413089f93ac0756a20f8e74ef1ab78c81ef13c" datatype="html">
@ -2782,7 +2782,7 @@
<target state="translated">Haut</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">165</context>
<context context-type="linenumber">197</context>
</context-group>
</trans-unit>
<trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
@ -2790,7 +2790,7 @@
<target state="translated">Bas</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">214</context>
<context context-type="linenumber">246</context>
</context-group>
</trans-unit>
<trans-unit id="f1a355a1af2e818050a3af693ac8b521fa7edc5f" datatype="html">
@ -2798,7 +2798,7 @@
<target state="translated">Évolution du Portefeuille</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">267</context>
<context context-type="linenumber">299</context>
</context-group>
</trans-unit>
<trans-unit id="658bfe96ba9d3a3a2ada478c8c855286b841f92a" datatype="html">
@ -2806,7 +2806,7 @@
<target state="translated">Historique des Investissements</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">294</context>
<context context-type="linenumber">326</context>
</context-group>
</trans-unit>
<trans-unit id="6410cffb96159fcff46d91effc26df0e240bc0e3" datatype="html">
@ -2814,7 +2814,7 @@
<target state="translated">Historique des Dividendes</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">351</context>
<context context-type="linenumber">383</context>
</context-group>
</trans-unit>
<trans-unit id="6382f0e408575450219d3a10f8883a8f6c67e7d7" datatype="html">
@ -3430,7 +3430,7 @@
<target state="translated">Annuel</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">51</context>
<context context-type="linenumber">55</context>
</context-group>
</trans-unit>
<trans-unit id="72640258012696878" datatype="html">
@ -4174,7 +4174,7 @@
<target state="translated">Série en cours</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">315</context>
<context context-type="linenumber">347</context>
</context-group>
</trans-unit>
<trans-unit id="eabb7b2ede5498042bc9fbb565981a780bf340dc" datatype="html">
@ -4182,7 +4182,7 @@
<target state="translated">Série la plus longue</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">324</context>
<context context-type="linenumber">356</context>
</context-group>
</trans-unit>
<trans-unit id="4845030128243887325" datatype="html">
@ -6311,11 +6311,11 @@
<target state="translated">Investissement</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">42</context>
<context context-type="linenumber">46</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">56</context>
<context context-type="linenumber">60</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts</context>
@ -6327,7 +6327,7 @@
<target state="translated">Performance des Actifs en valeur absolue</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">28</context>
<context context-type="linenumber">60</context>
</context-group>
</trans-unit>
<trans-unit id="c726a56ba67c6c788e3759983dd8a1671d8cc886" datatype="html">
@ -6335,7 +6335,7 @@
<target state="translated"> Performance des Actifs </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">49</context>
<context context-type="linenumber">81</context>
</context-group>
</trans-unit>
<trans-unit id="8ce52b52483f502dd23ed290357a17307c60280c" datatype="html">
@ -6343,7 +6343,7 @@
<target state="translated">Performance des devises en valeur absolue</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">71</context>
<context context-type="linenumber">103</context>
</context-group>
</trans-unit>
<trans-unit id="e4da628796a8880899b986c2af0559a55d6a700c" datatype="html">
@ -6351,7 +6351,7 @@
<target state="translated"> Performance des devises </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">95</context>
<context context-type="linenumber">127</context>
</context-group>
</trans-unit>
<trans-unit id="5584854134b3049db7dfb7bf4d87a0b9b9b4b149" datatype="html">
@ -6359,7 +6359,7 @@
<target state="translated"> Performance nette absolue </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">118</context>
<context context-type="linenumber">150</context>
</context-group>
</trans-unit>
<trans-unit id="d88d656d93dd2029b9d35712789d2567d2c0d739" datatype="html">
@ -6367,7 +6367,7 @@
<target state="translated"> Performance nette </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">137</context>
<context context-type="linenumber">169</context>
</context-group>
</trans-unit>
<trans-unit id="3105754554141014845" datatype="html">
@ -7636,6 +7636,22 @@
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit id="7826234236931647519" datatype="html">
<source>AI prompt has been copied to the clipboard</source>
<target state="new">AI prompt has been copied to the clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">149</context>
</context-group>
</trans-unit>
<trans-unit id="4eecb3acb016e7d86740405b21599cdad8e0dd9f" datatype="html">
<source>Copy AI prompt to clipboard</source>
<target state="new">Copy AI prompt to clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

52
apps/client/src/locales/messages.it.xlf

@ -2147,7 +2147,7 @@
<target state="translated">Cronologia degli investimenti</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">294</context>
<context context-type="linenumber">326</context>
</context-group>
</trans-unit>
<trans-unit id="6ae1c94f6bad274424f97e9bc8766242c1577447" datatype="html">
@ -2155,7 +2155,7 @@
<target state="translated">In alto</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">165</context>
<context context-type="linenumber">197</context>
</context-group>
</trans-unit>
<trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
@ -2163,7 +2163,7 @@
<target state="translated">In basso</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">214</context>
<context context-type="linenumber">246</context>
</context-group>
</trans-unit>
<trans-unit id="6382f0e408575450219d3a10f8883a8f6c67e7d7" datatype="html">
@ -2835,7 +2835,7 @@
<target state="translated">Mensile</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">50</context>
<context context-type="linenumber">54</context>
</context-group>
</trans-unit>
<trans-unit id="8511b16abcf065252b350d64e337ba2447db3ffb" datatype="html">
@ -2999,7 +2999,7 @@
<target state="translated">Evoluzione del portafoglio</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">267</context>
<context context-type="linenumber">299</context>
</context-group>
</trans-unit>
<trans-unit id="8192718423057883427" datatype="html">
@ -3315,7 +3315,7 @@
<target state="translated">Dividendi</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">38</context>
<context context-type="linenumber">41</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -3327,7 +3327,7 @@
<target state="translated">Cronologia dei dividendi</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">351</context>
<context context-type="linenumber">383</context>
</context-group>
</trans-unit>
<trans-unit id="7608037008789240367" datatype="html">
@ -3431,7 +3431,7 @@
<target state="translated">Annuale</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">51</context>
<context context-type="linenumber">55</context>
</context-group>
</trans-unit>
<trans-unit id="72640258012696878" datatype="html">
@ -4175,7 +4175,7 @@
<target state="translated">Serie attuale</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">315</context>
<context context-type="linenumber">347</context>
</context-group>
</trans-unit>
<trans-unit id="eabb7b2ede5498042bc9fbb565981a780bf340dc" datatype="html">
@ -4183,7 +4183,7 @@
<target state="translated">Serie più lunga</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">324</context>
<context context-type="linenumber">356</context>
</context-group>
</trans-unit>
<trans-unit id="4845030128243887325" datatype="html">
@ -6312,11 +6312,11 @@
<target state="translated">Investimento</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">42</context>
<context context-type="linenumber">46</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">56</context>
<context context-type="linenumber">60</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts</context>
@ -6328,7 +6328,7 @@
<target state="translated">Rendimento assoluto dell&apos;Asset</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">28</context>
<context context-type="linenumber">60</context>
</context-group>
</trans-unit>
<trans-unit id="c726a56ba67c6c788e3759983dd8a1671d8cc886" datatype="html">
@ -6336,7 +6336,7 @@
<target state="translated"> Rendimento dell&apos;Asset </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">49</context>
<context context-type="linenumber">81</context>
</context-group>
</trans-unit>
<trans-unit id="8ce52b52483f502dd23ed290357a17307c60280c" datatype="html">
@ -6344,7 +6344,7 @@
<target state="translated">Rendimento assoluto della Valuta</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">71</context>
<context context-type="linenumber">103</context>
</context-group>
</trans-unit>
<trans-unit id="e4da628796a8880899b986c2af0559a55d6a700c" datatype="html">
@ -6352,7 +6352,7 @@
<target state="translated"> Rendimento della Valuta </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">95</context>
<context context-type="linenumber">127</context>
</context-group>
</trans-unit>
<trans-unit id="5584854134b3049db7dfb7bf4d87a0b9b9b4b149" datatype="html">
@ -6360,7 +6360,7 @@
<target state="translated"> Rendimento assoluto della Valuta </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">118</context>
<context context-type="linenumber">150</context>
</context-group>
</trans-unit>
<trans-unit id="d88d656d93dd2029b9d35712789d2567d2c0d739" datatype="html">
@ -6368,7 +6368,7 @@
<target state="translated"> Rendimento Netto </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">137</context>
<context context-type="linenumber">169</context>
</context-group>
</trans-unit>
<trans-unit id="3105754554141014845" datatype="html">
@ -7637,6 +7637,22 @@
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit id="7826234236931647519" datatype="html">
<source>AI prompt has been copied to the clipboard</source>
<target state="new">AI prompt has been copied to the clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">149</context>
</context-group>
</trans-unit>
<trans-unit id="4eecb3acb016e7d86740405b21599cdad8e0dd9f" datatype="html">
<source>Copy AI prompt to clipboard</source>
<target state="new">Copy AI prompt to clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

52
apps/client/src/locales/messages.nl.xlf

@ -2146,7 +2146,7 @@
<target state="translated">Tijdlijn investeringen</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">294</context>
<context context-type="linenumber">326</context>
</context-group>
</trans-unit>
<trans-unit id="6ae1c94f6bad274424f97e9bc8766242c1577447" datatype="html">
@ -2154,7 +2154,7 @@
<target state="translated">Winnaars</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">165</context>
<context context-type="linenumber">197</context>
</context-group>
</trans-unit>
<trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
@ -2162,7 +2162,7 @@
<target state="translated">Verliezers</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">214</context>
<context context-type="linenumber">246</context>
</context-group>
</trans-unit>
<trans-unit id="6382f0e408575450219d3a10f8883a8f6c67e7d7" datatype="html">
@ -2834,7 +2834,7 @@
<target state="translated">Maandelijks</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">50</context>
<context context-type="linenumber">54</context>
</context-group>
</trans-unit>
<trans-unit id="8511b16abcf065252b350d64e337ba2447db3ffb" datatype="html">
@ -2998,7 +2998,7 @@
<target state="translated">Waardeontwikkeling van portefeuille</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">267</context>
<context context-type="linenumber">299</context>
</context-group>
</trans-unit>
<trans-unit id="8192718423057883427" datatype="html">
@ -3314,7 +3314,7 @@
<target state="translated">Dividend</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">38</context>
<context context-type="linenumber">41</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -3326,7 +3326,7 @@
<target state="translated">Tijdlijn dividend</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">351</context>
<context context-type="linenumber">383</context>
</context-group>
</trans-unit>
<trans-unit id="7608037008789240367" datatype="html">
@ -3430,7 +3430,7 @@
<target state="translated">Jaarlijks</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">51</context>
<context context-type="linenumber">55</context>
</context-group>
</trans-unit>
<trans-unit id="72640258012696878" datatype="html">
@ -4174,7 +4174,7 @@
<target state="translated">Huidige reeks</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">315</context>
<context context-type="linenumber">347</context>
</context-group>
</trans-unit>
<trans-unit id="eabb7b2ede5498042bc9fbb565981a780bf340dc" datatype="html">
@ -4182,7 +4182,7 @@
<target state="translated">Langste reeks</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">324</context>
<context context-type="linenumber">356</context>
</context-group>
</trans-unit>
<trans-unit id="4845030128243887325" datatype="html">
@ -6311,11 +6311,11 @@
<target state="new">Investment</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">42</context>
<context context-type="linenumber">46</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">56</context>
<context context-type="linenumber">60</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts</context>
@ -6327,7 +6327,7 @@
<target state="new">Absolute Asset Performance</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">28</context>
<context context-type="linenumber">60</context>
</context-group>
</trans-unit>
<trans-unit id="c726a56ba67c6c788e3759983dd8a1671d8cc886" datatype="html">
@ -6335,7 +6335,7 @@
<target state="new"> Asset Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">49</context>
<context context-type="linenumber">81</context>
</context-group>
</trans-unit>
<trans-unit id="8ce52b52483f502dd23ed290357a17307c60280c" datatype="html">
@ -6343,7 +6343,7 @@
<target state="new">Absolute Currency Performance</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">71</context>
<context context-type="linenumber">103</context>
</context-group>
</trans-unit>
<trans-unit id="e4da628796a8880899b986c2af0559a55d6a700c" datatype="html">
@ -6351,7 +6351,7 @@
<target state="new"> Currency Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">95</context>
<context context-type="linenumber">127</context>
</context-group>
</trans-unit>
<trans-unit id="5584854134b3049db7dfb7bf4d87a0b9b9b4b149" datatype="html">
@ -6359,7 +6359,7 @@
<target state="new"> Absolute Net Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">118</context>
<context context-type="linenumber">150</context>
</context-group>
</trans-unit>
<trans-unit id="d88d656d93dd2029b9d35712789d2567d2c0d739" datatype="html">
@ -6367,7 +6367,7 @@
<target state="new"> Net Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">137</context>
<context context-type="linenumber">169</context>
</context-group>
</trans-unit>
<trans-unit id="3105754554141014845" datatype="html">
@ -7636,6 +7636,22 @@
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit id="7826234236931647519" datatype="html">
<source>AI prompt has been copied to the clipboard</source>
<target state="new">AI prompt has been copied to the clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">149</context>
</context-group>
</trans-unit>
<trans-unit id="4eecb3acb016e7d86740405b21599cdad8e0dd9f" datatype="html">
<source>Copy AI prompt to clipboard</source>
<target state="new">Copy AI prompt to clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

52
apps/client/src/locales/messages.pl.xlf

@ -4659,7 +4659,7 @@
<target state="translated">Dywidenda</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">38</context>
<context context-type="linenumber">41</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -4679,7 +4679,7 @@
<target state="translated">Miesięcznie</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">50</context>
<context context-type="linenumber">54</context>
</context-group>
</trans-unit>
<trans-unit id="8036977202721714375" datatype="html">
@ -4687,7 +4687,7 @@
<target state="translated">Rocznie</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">51</context>
<context context-type="linenumber">55</context>
</context-group>
</trans-unit>
<trans-unit id="f6413089f93ac0756a20f8e74ef1ab78c81ef13c" datatype="html">
@ -4703,7 +4703,7 @@
<target state="translated">Największe wzrosty</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">165</context>
<context context-type="linenumber">197</context>
</context-group>
</trans-unit>
<trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
@ -4711,7 +4711,7 @@
<target state="translated">Największy spadek</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">214</context>
<context context-type="linenumber">246</context>
</context-group>
</trans-unit>
<trans-unit id="f1a355a1af2e818050a3af693ac8b521fa7edc5f" datatype="html">
@ -4719,7 +4719,7 @@
<target state="translated">Rozwój portfela</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">267</context>
<context context-type="linenumber">299</context>
</context-group>
</trans-unit>
<trans-unit id="658bfe96ba9d3a3a2ada478c8c855286b841f92a" datatype="html">
@ -4727,7 +4727,7 @@
<target state="translated">Oś czasu inwestycji</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">294</context>
<context context-type="linenumber">326</context>
</context-group>
</trans-unit>
<trans-unit id="1c275927e7e22395d21a86e4ab459e428bcac27e" datatype="html">
@ -4735,7 +4735,7 @@
<target state="translated">Obecna passa</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">315</context>
<context context-type="linenumber">347</context>
</context-group>
</trans-unit>
<trans-unit id="eabb7b2ede5498042bc9fbb565981a780bf340dc" datatype="html">
@ -4743,7 +4743,7 @@
<target state="translated">Najdłuższa passa</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">324</context>
<context context-type="linenumber">356</context>
</context-group>
</trans-unit>
<trans-unit id="6410cffb96159fcff46d91effc26df0e240bc0e3" datatype="html">
@ -4751,7 +4751,7 @@
<target state="translated">Oś czasu dywidend</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">351</context>
<context context-type="linenumber">383</context>
</context-group>
</trans-unit>
<trans-unit id="6382f0e408575450219d3a10f8883a8f6c67e7d7" datatype="html">
@ -6311,11 +6311,11 @@
<target state="translated">Inwestycje</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">42</context>
<context context-type="linenumber">46</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">56</context>
<context context-type="linenumber">60</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts</context>
@ -6327,7 +6327,7 @@
<target state="translated">Łączny wynik aktywów</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">28</context>
<context context-type="linenumber">60</context>
</context-group>
</trans-unit>
<trans-unit id="c726a56ba67c6c788e3759983dd8a1671d8cc886" datatype="html">
@ -6335,7 +6335,7 @@
<target state="translated"> Wyniki aktywów </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">49</context>
<context context-type="linenumber">81</context>
</context-group>
</trans-unit>
<trans-unit id="8ce52b52483f502dd23ed290357a17307c60280c" datatype="html">
@ -6343,7 +6343,7 @@
<target state="translated">Łączny wynik walut</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">71</context>
<context context-type="linenumber">103</context>
</context-group>
</trans-unit>
<trans-unit id="e4da628796a8880899b986c2af0559a55d6a700c" datatype="html">
@ -6351,7 +6351,7 @@
<target state="translated"> Wynik walut </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">95</context>
<context context-type="linenumber">127</context>
</context-group>
</trans-unit>
<trans-unit id="5584854134b3049db7dfb7bf4d87a0b9b9b4b149" datatype="html">
@ -6359,7 +6359,7 @@
<target state="translated"> Łączna wartość netto </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">118</context>
<context context-type="linenumber">150</context>
</context-group>
</trans-unit>
<trans-unit id="d88d656d93dd2029b9d35712789d2567d2c0d739" datatype="html">
@ -6367,7 +6367,7 @@
<target state="translated"> Wynik netto </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">137</context>
<context context-type="linenumber">169</context>
</context-group>
</trans-unit>
<trans-unit id="3105754554141014845" datatype="html">
@ -7636,6 +7636,22 @@
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit id="7826234236931647519" datatype="html">
<source>AI prompt has been copied to the clipboard</source>
<target state="new">AI prompt has been copied to the clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">149</context>
</context-group>
</trans-unit>
<trans-unit id="4eecb3acb016e7d86740405b21599cdad8e0dd9f" datatype="html">
<source>Copy AI prompt to clipboard</source>
<target state="new">Copy AI prompt to clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

52
apps/client/src/locales/messages.pt.xlf

@ -2654,7 +2654,7 @@
<target state="translated">Mensalmente</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">50</context>
<context context-type="linenumber">54</context>
</context-group>
</trans-unit>
<trans-unit id="f6413089f93ac0756a20f8e74ef1ab78c81ef13c" datatype="html">
@ -2670,7 +2670,7 @@
<target state="translated">Topo</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">165</context>
<context context-type="linenumber">197</context>
</context-group>
</trans-unit>
<trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
@ -2678,7 +2678,7 @@
<target state="translated">Fundo</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">214</context>
<context context-type="linenumber">246</context>
</context-group>
</trans-unit>
<trans-unit id="f1a355a1af2e818050a3af693ac8b521fa7edc5f" datatype="html">
@ -2686,7 +2686,7 @@
<target state="translated">Evolução do Portefólio</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">267</context>
<context context-type="linenumber">299</context>
</context-group>
</trans-unit>
<trans-unit id="658bfe96ba9d3a3a2ada478c8c855286b841f92a" datatype="html">
@ -2694,7 +2694,7 @@
<target state="translated">Cronograma de Investimento</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">294</context>
<context context-type="linenumber">326</context>
</context-group>
</trans-unit>
<trans-unit id="6382f0e408575450219d3a10f8883a8f6c67e7d7" datatype="html">
@ -3382,7 +3382,7 @@
<target state="translated">Dividendos</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">38</context>
<context context-type="linenumber">41</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -3394,7 +3394,7 @@
<target state="translated">Cronograma de Dividendos</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">351</context>
<context context-type="linenumber">383</context>
</context-group>
</trans-unit>
<trans-unit id="7608037008789240367" datatype="html">
@ -3430,7 +3430,7 @@
<target state="translated">Anualmente</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">51</context>
<context context-type="linenumber">55</context>
</context-group>
</trans-unit>
<trans-unit id="72640258012696878" datatype="html">
@ -4174,7 +4174,7 @@
<target state="translated">Série Atual</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">315</context>
<context context-type="linenumber">347</context>
</context-group>
</trans-unit>
<trans-unit id="eabb7b2ede5498042bc9fbb565981a780bf340dc" datatype="html">
@ -4182,7 +4182,7 @@
<target state="translated">Série mais Longa</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">324</context>
<context context-type="linenumber">356</context>
</context-group>
</trans-unit>
<trans-unit id="4845030128243887325" datatype="html">
@ -6311,11 +6311,11 @@
<target state="new">Investment</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">42</context>
<context context-type="linenumber">46</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">56</context>
<context context-type="linenumber">60</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts</context>
@ -6327,7 +6327,7 @@
<target state="new">Absolute Asset Performance</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">28</context>
<context context-type="linenumber">60</context>
</context-group>
</trans-unit>
<trans-unit id="c726a56ba67c6c788e3759983dd8a1671d8cc886" datatype="html">
@ -6335,7 +6335,7 @@
<target state="new"> Asset Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">49</context>
<context context-type="linenumber">81</context>
</context-group>
</trans-unit>
<trans-unit id="8ce52b52483f502dd23ed290357a17307c60280c" datatype="html">
@ -6343,7 +6343,7 @@
<target state="new">Absolute Currency Performance</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">71</context>
<context context-type="linenumber">103</context>
</context-group>
</trans-unit>
<trans-unit id="e4da628796a8880899b986c2af0559a55d6a700c" datatype="html">
@ -6351,7 +6351,7 @@
<target state="new"> Currency Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">95</context>
<context context-type="linenumber">127</context>
</context-group>
</trans-unit>
<trans-unit id="5584854134b3049db7dfb7bf4d87a0b9b9b4b149" datatype="html">
@ -6359,7 +6359,7 @@
<target state="new"> Absolute Net Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">118</context>
<context context-type="linenumber">150</context>
</context-group>
</trans-unit>
<trans-unit id="d88d656d93dd2029b9d35712789d2567d2c0d739" datatype="html">
@ -6367,7 +6367,7 @@
<target state="new"> Net Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">137</context>
<context context-type="linenumber">169</context>
</context-group>
</trans-unit>
<trans-unit id="3105754554141014845" datatype="html">
@ -7636,6 +7636,22 @@
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit id="7826234236931647519" datatype="html">
<source>AI prompt has been copied to the clipboard</source>
<target state="new">AI prompt has been copied to the clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">149</context>
</context-group>
</trans-unit>
<trans-unit id="4eecb3acb016e7d86740405b21599cdad8e0dd9f" datatype="html">
<source>Copy AI prompt to clipboard</source>
<target state="new">Copy AI prompt to clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

52
apps/client/src/locales/messages.tr.xlf

@ -4147,7 +4147,7 @@
<target state="translated">Temettü</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">38</context>
<context context-type="linenumber">41</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -4167,7 +4167,7 @@
<target state="translated">Aylık</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">50</context>
<context context-type="linenumber">54</context>
</context-group>
</trans-unit>
<trans-unit id="8036977202721714375" datatype="html">
@ -4175,7 +4175,7 @@
<target state="translated">Yıllık</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">51</context>
<context context-type="linenumber">55</context>
</context-group>
</trans-unit>
<trans-unit id="f6413089f93ac0756a20f8e74ef1ab78c81ef13c" datatype="html">
@ -4191,7 +4191,7 @@
<target state="translated">Üst</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">165</context>
<context context-type="linenumber">197</context>
</context-group>
</trans-unit>
<trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
@ -4199,7 +4199,7 @@
<target state="translated">Alt</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">214</context>
<context context-type="linenumber">246</context>
</context-group>
</trans-unit>
<trans-unit id="f1a355a1af2e818050a3af693ac8b521fa7edc5f" datatype="html">
@ -4207,7 +4207,7 @@
<target state="translated">Portföyün Gelişimi</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">267</context>
<context context-type="linenumber">299</context>
</context-group>
</trans-unit>
<trans-unit id="658bfe96ba9d3a3a2ada478c8c855286b841f92a" datatype="html">
@ -4215,7 +4215,7 @@
<target state="translated">Yatırım Zaman Çizelgesi</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">294</context>
<context context-type="linenumber">326</context>
</context-group>
</trans-unit>
<trans-unit id="1c275927e7e22395d21a86e4ab459e428bcac27e" datatype="html">
@ -4223,7 +4223,7 @@
<target state="translated">Güncel Seri</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">315</context>
<context context-type="linenumber">347</context>
</context-group>
</trans-unit>
<trans-unit id="eabb7b2ede5498042bc9fbb565981a780bf340dc" datatype="html">
@ -4231,7 +4231,7 @@
<target state="translated">En Uzun Seri</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">324</context>
<context context-type="linenumber">356</context>
</context-group>
</trans-unit>
<trans-unit id="6410cffb96159fcff46d91effc26df0e240bc0e3" datatype="html">
@ -4239,7 +4239,7 @@
<target state="translated">Temettü Zaman Çizelgesi</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">351</context>
<context context-type="linenumber">383</context>
</context-group>
</trans-unit>
<trans-unit id="6382f0e408575450219d3a10f8883a8f6c67e7d7" datatype="html">
@ -6311,11 +6311,11 @@
<target state="new">Investment</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">42</context>
<context context-type="linenumber">46</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">56</context>
<context context-type="linenumber">60</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts</context>
@ -6327,7 +6327,7 @@
<target state="new">Absolute Asset Performance</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">28</context>
<context context-type="linenumber">60</context>
</context-group>
</trans-unit>
<trans-unit id="c726a56ba67c6c788e3759983dd8a1671d8cc886" datatype="html">
@ -6335,7 +6335,7 @@
<target state="new"> Asset Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">49</context>
<context context-type="linenumber">81</context>
</context-group>
</trans-unit>
<trans-unit id="8ce52b52483f502dd23ed290357a17307c60280c" datatype="html">
@ -6343,7 +6343,7 @@
<target state="new">Absolute Currency Performance</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">71</context>
<context context-type="linenumber">103</context>
</context-group>
</trans-unit>
<trans-unit id="e4da628796a8880899b986c2af0559a55d6a700c" datatype="html">
@ -6351,7 +6351,7 @@
<target state="new"> Currency Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">95</context>
<context context-type="linenumber">127</context>
</context-group>
</trans-unit>
<trans-unit id="5584854134b3049db7dfb7bf4d87a0b9b9b4b149" datatype="html">
@ -6359,7 +6359,7 @@
<target state="new"> Absolute Net Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">118</context>
<context context-type="linenumber">150</context>
</context-group>
</trans-unit>
<trans-unit id="d88d656d93dd2029b9d35712789d2567d2c0d739" datatype="html">
@ -6367,7 +6367,7 @@
<target state="new"> Net Performance </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">137</context>
<context context-type="linenumber">169</context>
</context-group>
</trans-unit>
<trans-unit id="3105754554141014845" datatype="html">
@ -7636,6 +7636,22 @@
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit id="7826234236931647519" datatype="html">
<source>AI prompt has been copied to the clipboard</source>
<target state="new">AI prompt has been copied to the clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">149</context>
</context-group>
</trans-unit>
<trans-unit id="4eecb3acb016e7d86740405b21599cdad8e0dd9f" datatype="html">
<source>Copy AI prompt to clipboard</source>
<target state="new">Copy AI prompt to clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

50
apps/client/src/locales/messages.xlf

@ -4286,7 +4286,7 @@
<source>Dividend</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">38</context>
<context context-type="linenumber">41</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -4304,14 +4304,14 @@
<source>Monthly</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">50</context>
<context context-type="linenumber">54</context>
</context-group>
</trans-unit>
<trans-unit id="8036977202721714375" datatype="html">
<source>Yearly</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">51</context>
<context context-type="linenumber">55</context>
</context-group>
</trans-unit>
<trans-unit id="f6413089f93ac0756a20f8e74ef1ab78c81ef13c" datatype="html">
@ -4325,49 +4325,49 @@
<source>Top</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">165</context>
<context context-type="linenumber">197</context>
</context-group>
</trans-unit>
<trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
<source>Bottom</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">214</context>
<context context-type="linenumber">246</context>
</context-group>
</trans-unit>
<trans-unit id="f1a355a1af2e818050a3af693ac8b521fa7edc5f" datatype="html">
<source>Portfolio Evolution</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">267</context>
<context context-type="linenumber">299</context>
</context-group>
</trans-unit>
<trans-unit id="658bfe96ba9d3a3a2ada478c8c855286b841f92a" datatype="html">
<source>Investment Timeline</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">294</context>
<context context-type="linenumber">326</context>
</context-group>
</trans-unit>
<trans-unit id="1c275927e7e22395d21a86e4ab459e428bcac27e" datatype="html">
<source>Current Streak</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">315</context>
<context context-type="linenumber">347</context>
</context-group>
</trans-unit>
<trans-unit id="eabb7b2ede5498042bc9fbb565981a780bf340dc" datatype="html">
<source>Longest Streak</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">324</context>
<context context-type="linenumber">356</context>
</context-group>
</trans-unit>
<trans-unit id="6410cffb96159fcff46d91effc26df0e240bc0e3" datatype="html">
<source>Dividend Timeline</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">351</context>
<context context-type="linenumber">383</context>
</context-group>
</trans-unit>
<trans-unit id="6382f0e408575450219d3a10f8883a8f6c67e7d7" datatype="html">
@ -5743,32 +5743,32 @@
<source>Absolute Currency Performance</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">71</context>
<context context-type="linenumber">103</context>
</context-group>
</trans-unit>
<trans-unit id="5584854134b3049db7dfb7bf4d87a0b9b9b4b149" datatype="html">
<source> Absolute Net Performance </source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">118</context>
<context context-type="linenumber">150</context>
</context-group>
</trans-unit>
<trans-unit id="e19955db970092b9cde70be2cea163ab6adfac97" datatype="html">
<source>Absolute Asset Performance</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">28</context>
<context context-type="linenumber">60</context>
</context-group>
</trans-unit>
<trans-unit id="8580549503047096056" datatype="html">
<source>Investment</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">42</context>
<context context-type="linenumber">46</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">56</context>
<context context-type="linenumber">60</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts</context>
@ -5779,21 +5779,21 @@
<source> Asset Performance </source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">49</context>
<context context-type="linenumber">81</context>
</context-group>
</trans-unit>
<trans-unit id="d88d656d93dd2029b9d35712789d2567d2c0d739" datatype="html">
<source> Net Performance </source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">137</context>
<context context-type="linenumber">169</context>
</context-group>
</trans-unit>
<trans-unit id="e4da628796a8880899b986c2af0559a55d6a700c" datatype="html">
<source> Currency Performance </source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">95</context>
<context context-type="linenumber">127</context>
</context-group>
</trans-unit>
<trans-unit id="2593751087640318641" datatype="html">
@ -6908,6 +6908,20 @@
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit id="4eecb3acb016e7d86740405b21599cdad8e0dd9f" datatype="html">
<source>Copy AI prompt to clipboard</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="7826234236931647519" datatype="html">
<source>AI prompt has been copied to the clipboard</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">149</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

52
apps/client/src/locales/messages.zh.xlf

@ -4676,7 +4676,7 @@
<target state="translated">股息</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">38</context>
<context context-type="linenumber">41</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -4696,7 +4696,7 @@
<target state="translated">每月</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">50</context>
<context context-type="linenumber">54</context>
</context-group>
</trans-unit>
<trans-unit id="8036977202721714375" datatype="html">
@ -4704,7 +4704,7 @@
<target state="translated">每年</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">51</context>
<context context-type="linenumber">55</context>
</context-group>
</trans-unit>
<trans-unit id="f6413089f93ac0756a20f8e74ef1ab78c81ef13c" datatype="html">
@ -4720,7 +4720,7 @@
<target state="translated">顶部</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">165</context>
<context context-type="linenumber">197</context>
</context-group>
</trans-unit>
<trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
@ -4728,7 +4728,7 @@
<target state="translated">底部</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">214</context>
<context context-type="linenumber">246</context>
</context-group>
</trans-unit>
<trans-unit id="f1a355a1af2e818050a3af693ac8b521fa7edc5f" datatype="html">
@ -4736,7 +4736,7 @@
<target state="translated">投资组合演变</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">267</context>
<context context-type="linenumber">299</context>
</context-group>
</trans-unit>
<trans-unit id="658bfe96ba9d3a3a2ada478c8c855286b841f92a" datatype="html">
@ -4744,7 +4744,7 @@
<target state="translated">投资时间表</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">294</context>
<context context-type="linenumber">326</context>
</context-group>
</trans-unit>
<trans-unit id="1c275927e7e22395d21a86e4ab459e428bcac27e" datatype="html">
@ -4752,7 +4752,7 @@
<target state="translated">当前连胜</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">315</context>
<context context-type="linenumber">347</context>
</context-group>
</trans-unit>
<trans-unit id="eabb7b2ede5498042bc9fbb565981a780bf340dc" datatype="html">
@ -4760,7 +4760,7 @@
<target state="translated">最长连续纪录</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">324</context>
<context context-type="linenumber">356</context>
</context-group>
</trans-unit>
<trans-unit id="6410cffb96159fcff46d91effc26df0e240bc0e3" datatype="html">
@ -4768,7 +4768,7 @@
<target state="translated">股息时间表</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">351</context>
<context context-type="linenumber">383</context>
</context-group>
</trans-unit>
<trans-unit id="6382f0e408575450219d3a10f8883a8f6c67e7d7" datatype="html">
@ -6312,7 +6312,7 @@
<target state="translated">绝对货币表现</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">71</context>
<context context-type="linenumber">103</context>
</context-group>
</trans-unit>
<trans-unit id="5584854134b3049db7dfb7bf4d87a0b9b9b4b149" datatype="html">
@ -6320,7 +6320,7 @@
<target state="translated">绝对净性能</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">118</context>
<context context-type="linenumber">150</context>
</context-group>
</trans-unit>
<trans-unit id="e19955db970092b9cde70be2cea163ab6adfac97" datatype="html">
@ -6328,7 +6328,7 @@
<target state="translated">绝对资产绩效</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">28</context>
<context context-type="linenumber">60</context>
</context-group>
</trans-unit>
<trans-unit id="8580549503047096056" datatype="html">
@ -6336,11 +6336,11 @@
<target state="translated">投资</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">42</context>
<context context-type="linenumber">46</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">56</context>
<context context-type="linenumber">60</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts</context>
@ -6352,7 +6352,7 @@
<target state="translated">资产绩效</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">49</context>
<context context-type="linenumber">81</context>
</context-group>
</trans-unit>
<trans-unit id="d88d656d93dd2029b9d35712789d2567d2c0d739" datatype="html">
@ -6360,7 +6360,7 @@
<target state="translated">净绩效</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">137</context>
<context context-type="linenumber">169</context>
</context-group>
</trans-unit>
<trans-unit id="e4da628796a8880899b986c2af0559a55d6a700c" datatype="html">
@ -6368,7 +6368,7 @@
<target state="translated">货币表现</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">95</context>
<context context-type="linenumber">127</context>
</context-group>
</trans-unit>
<trans-unit id="2593751087640318641" datatype="html">
@ -7637,6 +7637,22 @@
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit id="7826234236931647519" datatype="html">
<source>AI prompt has been copied to the clipboard</source>
<target state="new">AI prompt has been copied to the clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">149</context>
</context-group>
</trans-unit>
<trans-unit id="4eecb3acb016e7d86740405b21599cdad8e0dd9f" datatype="html">
<source>Copy AI prompt to clipboard</source>
<target state="new">Copy AI prompt to clipboard</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

2
libs/common/src/lib/interfaces/index.ts

@ -38,6 +38,7 @@ import type { PortfolioSummary } from './portfolio-summary.interface';
import type { Position } from './position.interface';
import type { Product } from './product';
import type { AccountBalancesResponse } from './responses/account-balances-response.interface';
import type { AiPromptResponse } from './responses/ai-prompt-response.interface';
import type { ApiKeyResponse } from './responses/api-key-response.interface';
import type { BenchmarkResponse } from './responses/benchmark-response.interface';
import type { DataProviderGhostfolioStatusResponse } from './responses/data-provider-ghostfolio-status-response.interface';
@ -74,6 +75,7 @@ export {
AdminMarketDataDetails,
AdminMarketDataItem,
AdminUsers,
AiPromptResponse,
ApiKeyResponse,
AssetProfileIdentifier,
Benchmark,

3
libs/common/src/lib/interfaces/responses/ai-prompt-response.interface.ts

@ -0,0 +1,3 @@
export interface AiPromptResponse {
prompt: string;
}

6
libs/common/src/lib/permissions.ts

@ -35,6 +35,7 @@ export const permissions = {
enableSubscriptionInterstitial: 'enableSubscriptionInterstitial',
enableSystemMessage: 'enableSystemMessage',
impersonateAllUsers: 'impersonateAllUsers',
readAiPrompt: 'readAiPrompt',
readMarketData: 'readMarketData',
readMarketDataOfOwnAssetProfile: 'readMarketDataOfOwnAssetProfile',
readPlatforms: 'readPlatforms',
@ -76,6 +77,7 @@ export function getPermissions(aRole: Role): string[] {
permissions.deletePlatform,
permissions.deleteTag,
permissions.deleteUser,
permissions.readAiPrompt,
permissions.readMarketData,
permissions.readMarketDataOfOwnAssetProfile,
permissions.readPlatforms,
@ -95,7 +97,8 @@ export function getPermissions(aRole: Role): string[] {
return [
permissions.accessAssistant,
permissions.accessHoldingsChart,
permissions.createUserAccount
permissions.createUserAccount,
permissions.readAiPrompt
];
case 'USER':
@ -113,6 +116,7 @@ export function getPermissions(aRole: Role): string[] {
permissions.deleteAuthDevice,
permissions.deleteOrder,
permissions.deleteOwnUser,
permissions.readAiPrompt,
permissions.readMarketDataOfOwnAssetProfile,
permissions.updateAccount,
permissions.updateAuthDevice,

31
libs/ui/src/lib/fire-calculator/fire-calculator.component.stories.ts

@ -4,47 +4,48 @@ import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import '@angular/localize/init';
import { MatButtonModule } from '@angular/material/button';
import { provideNativeDateAdapter } from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { Meta, Story, moduleMetadata } from '@storybook/angular';
import { moduleMetadata } from '@storybook/angular';
import type { Meta, StoryObj } from '@storybook/angular';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { GfValueModule } from '../value';
import { FireCalculatorComponent } from './fire-calculator.component';
import { GfValueComponent } from '../value';
import { GfFireCalculatorComponent } from './fire-calculator.component';
import { FireCalculatorService } from './fire-calculator.service';
export default {
title: 'FIRE Calculator',
component: FireCalculatorComponent,
component: GfFireCalculatorComponent,
decorators: [
moduleMetadata({
declarations: [FireCalculatorComponent],
imports: [
CommonModule,
FormsModule,
GfValueModule,
GfFireCalculatorComponent,
GfValueComponent,
MatButtonModule,
MatDatepickerModule,
MatFormFieldModule,
MatInputModule,
NgxSkeletonLoaderModule,
NoopAnimationsModule,
ReactiveFormsModule
],
providers: [FireCalculatorService]
providers: [FireCalculatorService, provideNativeDateAdapter()]
})
]
} as Meta<FireCalculatorComponent>;
} as Meta<GfFireCalculatorComponent>;
const Template: Story<FireCalculatorComponent> = (
args: FireCalculatorComponent
) => ({
props: args
});
type Story = StoryObj<GfFireCalculatorComponent>;
export const Simple = Template.bind({});
Simple.args = {
export const Simple: Story = {
args: {
currency: 'USD',
fireWealth: 0,
locale: locale
}
};

21
libs/ui/src/lib/line-chart/line-chart.component.stories.ts

@ -1,26 +1,24 @@
import { CommonModule } from '@angular/common';
import { Meta, Story, moduleMetadata } from '@storybook/angular';
import { moduleMetadata } from '@storybook/angular';
import type { Meta, StoryObj } from '@storybook/angular';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { LineChartComponent } from './line-chart.component';
import { GfLineChartComponent } from './line-chart.component';
export default {
title: 'Line Chart',
component: LineChartComponent,
component: GfLineChartComponent,
decorators: [
moduleMetadata({
declarations: [LineChartComponent],
imports: [CommonModule, NgxSkeletonLoaderModule]
imports: [CommonModule, GfLineChartComponent, NgxSkeletonLoaderModule]
})
]
} as Meta<LineChartComponent>;
} as Meta<GfLineChartComponent>;
const Template: Story<LineChartComponent> = (args: LineChartComponent) => ({
props: args
});
type Story = StoryObj<GfLineChartComponent>;
export const Simple = Template.bind({});
Simple.args = {
export const Simple: Story = {
args: {
historicalDataItems: [
{
date: '2017-01-01',
@ -232,4 +230,5 @@ Simple.args = {
}
],
isAnimated: true
}
};

28
libs/ui/src/lib/logo/logo.component.stories.ts

@ -1,30 +1,32 @@
import { Meta, Story, moduleMetadata } from '@storybook/angular';
import { moduleMetadata } from '@storybook/angular';
import type { Meta, StoryObj } from '@storybook/angular';
import { LogoComponent } from './logo.component';
import { GfLogoComponent } from './logo.component';
export default {
title: 'Logo',
component: LogoComponent,
component: GfLogoComponent,
decorators: [
moduleMetadata({
imports: []
})
]
} as Meta<LogoComponent>;
} as Meta<GfLogoComponent>;
const Template: Story<LogoComponent> = (args: LogoComponent) => ({
props: args
});
type Story = StoryObj<GfLogoComponent>;
export const Default = Template.bind({});
Default.args = {};
export const Default: Story = {
args: {}
};
export const Large = Template.bind({});
Large.args = {
export const Large: Story = {
args: {
size: 'large'
}
};
export const NoLabel = Template.bind({});
NoLabel.args = {
export const NoLabel: Story = {
args: {
showLabel: false
}
};

24
libs/ui/src/lib/no-transactions-info/no-transactions-info.component.stories.ts

@ -1,25 +1,23 @@
import { GfLogoModule } from '@ghostfolio/ui/logo';
import { GfLogoComponent } from '@ghostfolio/ui/logo';
import { RouterTestingModule } from '@angular/router/testing';
import { Meta, Story, moduleMetadata } from '@storybook/angular';
import { moduleMetadata } from '@storybook/angular';
import type { Meta, StoryObj } from '@storybook/angular';
import { NoTransactionsInfoComponent } from './no-transactions-info.component';
import { GfNoTransactionsInfoComponent } from './no-transactions-info.component';
export default {
title: 'No Transactions Info',
component: NoTransactionsInfoComponent,
component: GfNoTransactionsInfoComponent,
decorators: [
moduleMetadata({
imports: [GfLogoModule, RouterTestingModule]
imports: [GfLogoComponent, RouterTestingModule]
})
]
} as Meta<NoTransactionsInfoComponent>;
} as Meta<GfNoTransactionsInfoComponent>;
const Template: Story<NoTransactionsInfoComponent> = (
args: NoTransactionsInfoComponent
) => ({
props: args
});
type Story = StoryObj<GfNoTransactionsInfoComponent>;
export const Default = Template.bind({});
Default.args = {};
export const Default: Story = {
args: {}
};

27
libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.stories.ts

@ -1,29 +1,29 @@
import { CommonModule } from '@angular/common';
import '@angular/localize/init';
import { Meta, Story, moduleMetadata } from '@storybook/angular';
import { moduleMetadata } from '@storybook/angular';
import type { Meta, StoryObj } from '@storybook/angular';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { PortfolioProportionChartComponent } from './portfolio-proportion-chart.component';
import { GfPortfolioProportionChartComponent } from './portfolio-proportion-chart.component';
export default {
title: 'Portfolio Proportion Chart',
component: PortfolioProportionChartComponent,
component: GfPortfolioProportionChartComponent,
decorators: [
moduleMetadata({
declarations: [PortfolioProportionChartComponent],
imports: [CommonModule, NgxSkeletonLoaderModule]
imports: [
CommonModule,
GfPortfolioProportionChartComponent,
NgxSkeletonLoaderModule
]
})
]
} as Meta<PortfolioProportionChartComponent>;
} as Meta<GfPortfolioProportionChartComponent>;
const Template: Story<PortfolioProportionChartComponent> = (
args: PortfolioProportionChartComponent
) => ({
props: args
});
type Story = StoryObj<GfPortfolioProportionChartComponent>;
export const Simple = Template.bind({});
Simple.args = {
export const Simple: Story = {
args: {
baseCurrency: 'USD',
keys: ['name'],
locale: 'en-US',
@ -35,4 +35,5 @@ Simple.args = {
Oceania: { name: 'Oceania', value: 1402.220605072031 },
'South America': { name: 'South America', value: 4938.25202180719859 }
}
}
};

25
libs/ui/src/lib/premium-indicator/premium-indicator.component.stories.ts

@ -1,29 +1,28 @@
import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { Meta, Story, moduleMetadata } from '@storybook/angular';
import { moduleMetadata } from '@storybook/angular';
import type { Meta, StoryObj } from '@storybook/angular';
import { PremiumIndicatorComponent } from './premium-indicator.component';
import { GfPremiumIndicatorComponent } from './premium-indicator.component';
export default {
title: 'Premium Indicator',
component: PremiumIndicatorComponent,
component: GfPremiumIndicatorComponent,
decorators: [
moduleMetadata({
imports: [CommonModule, RouterTestingModule]
})
]
} as Meta<PremiumIndicatorComponent>;
} as Meta<GfPremiumIndicatorComponent>;
const Template: Story<PremiumIndicatorComponent> = (
args: PremiumIndicatorComponent
) => ({
props: args
});
type Story = StoryObj<GfPremiumIndicatorComponent>;
export const Default = Template.bind({});
Default.args = {};
export const Default: Story = {
args: {}
};
export const WithoutLink = Template.bind({});
WithoutLink.args = {
export const WithoutLink = {
args: {
enableLink: false
}
};

53
libs/ui/src/lib/trend-indicator/trend-indicator.component.stories.ts

@ -1,50 +1,53 @@
import { Meta, Story, moduleMetadata } from '@storybook/angular';
import { moduleMetadata } from '@storybook/angular';
import type { Meta, StoryObj } from '@storybook/angular';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { TrendIndicatorComponent } from './trend-indicator.component';
import { GfTrendIndicatorComponent } from './trend-indicator.component';
export default {
title: 'Trend Indicator',
component: TrendIndicatorComponent,
component: GfTrendIndicatorComponent,
decorators: [
moduleMetadata({
imports: [NgxSkeletonLoaderModule]
})
]
} as Meta<TrendIndicatorComponent>;
} as Meta<GfTrendIndicatorComponent>;
const Template: Story<TrendIndicatorComponent> = (
args: TrendIndicatorComponent
) => ({
props: args
});
type Story = StoryObj<GfTrendIndicatorComponent>;
export const Loading = Template.bind({});
Loading.args = {
export const Loading: Story = {
args: {
isLoading: true
}
};
export const Default = Template.bind({});
Default.args = {};
export const Default: Story = {
args: {}
};
export const Delayed = Template.bind({});
Delayed.args = {
marketState: 'delayed',
range: '1d'
export const Delayed: Story = {
args: {
dateRange: '1d',
marketState: 'delayed'
}
};
export const Down = Template.bind({});
Down.args = {
export const Down: Story = {
args: {
value: -1
}
};
export const Up = Template.bind({});
Up.args = {
export const Up: Story = {
args: {
value: 1
}
};
export const MarketClosed = Template.bind({});
MarketClosed.args = {
marketState: 'closed',
range: '1d'
export const MarketClosed: Story = {
args: {
dateRange: '1d',
marketState: 'closed'
}
};

60
libs/ui/src/lib/value/value.component.stories.ts

@ -1,71 +1,77 @@
import { Meta, Story, moduleMetadata } from '@storybook/angular';
import { moduleMetadata } from '@storybook/angular';
import type { Meta, StoryObj } from '@storybook/angular';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { ValueComponent } from './value.component';
import { GfValueComponent } from './value.component';
export default {
title: 'Value',
component: ValueComponent,
component: GfValueComponent,
decorators: [
moduleMetadata({
imports: [NgxSkeletonLoaderModule]
})
]
} as Meta<ValueComponent>;
} as Meta<GfValueComponent>;
const Template: Story<ValueComponent> = (args: ValueComponent) => ({
props: args
});
type Story = StoryObj<GfValueComponent>;
export const Loading = Template.bind({});
Loading.args = {
export const Loading: Story = {
args: {
value: undefined
}
};
export const Currency = Template.bind({});
Currency.args = {
export const Currency: Story = {
args: {
isCurrency: true,
locale: 'en-US',
unit: 'USD',
value: 7
}
};
export const Label = Template.bind({});
Label.args = {
export const Label: Story = {
args: {
locale: 'en-US',
value: 7.25
}
};
export const PerformancePositive = Template.bind({});
PerformancePositive.args = {
locale: 'en-US',
export const PerformancePositive: Story = {
args: {
colorizeSign: true,
isPercent: true,
locale: 'en-US',
value: 0.0136810853673890378
},
name: 'Performance (positive)'
};
PerformancePositive.storyName = 'Performance (positive)';
export const PerformanceNegative = Template.bind({});
PerformanceNegative.args = {
locale: 'en-US',
export const PerformanceNegative: Story = {
args: {
colorizeSign: true,
isPercent: true,
locale: 'en-US',
value: -0.0136810853673890378
},
name: 'Performance (negative)'
};
PerformanceNegative.storyName = 'Performance (negative)';
export const PerformanceCloseToZero = Template.bind({});
PerformanceCloseToZero.args = {
locale: 'en-US',
export const PerformanceCloseToZero: Story = {
args: {
colorizeSign: true,
isPercent: true,
locale: 'en-US',
value: -2.388915360475e-8
},
name: 'Performance (negative zero)'
};
PerformanceCloseToZero.storyName = 'Performance (negative zero)';
export const Precision = Template.bind({});
Precision.args = {
export const Precision: Story = {
args: {
locale: 'en-US',
precision: 3,
value: 7.2534802394809285309
}
};

383
package-lock.json

@ -1,12 +1,12 @@
{
"name": "ghostfolio",
"version": "2.132.0",
"version": "2.133.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ghostfolio",
"version": "2.132.0",
"version": "2.133.1",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {
@ -63,9 +63,8 @@
"countries-list": "3.1.1",
"countup.js": "2.8.0",
"date-fns": "3.6.0",
"envalid": "7.3.1",
"envalid": "8.0.0",
"google-spreadsheet": "3.2.0",
"got": "11.8.6",
"helmet": "7.0.0",
"http-status-codes": "2.3.0",
"ionicons": "7.4.0",
@ -127,7 +126,7 @@
"@trivago/prettier-plugin-sort-imports": "4.3.0",
"@types/big.js": "6.2.2",
"@types/cache-manager": "4.0.6",
"@types/color": "3.0.6",
"@types/color": "4.2.0",
"@types/google-spreadsheet": "3.1.5",
"@types/jest": "29.5.13",
"@types/lodash": "4.17.7",
@ -153,7 +152,7 @@
"prisma": "6.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"replace-in-file": "7.0.1",
"replace-in-file": "8.3.0",
"shx": "0.3.4",
"storybook": "8.4.7",
"ts-jest": "29.1.0",
@ -9561,18 +9560,6 @@
"devOptional": true,
"license": "MIT"
},
"node_modules/@sindresorhus/is": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
"integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/@sindresorhus/merge-streams": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz",
@ -10643,18 +10630,6 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/@szmarczak/http-timer": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
"integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
"license": "MIT",
"dependencies": {
"defer-to-connect": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@testing-library/dom": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
@ -11148,37 +11123,10 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/cacheable-request": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
"integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
"license": "MIT",
"dependencies": {
"@types/http-cache-semantics": "*",
"@types/keyv": "^3.1.4",
"@types/node": "*",
"@types/responselike": "^1.0.0"
}
},
"node_modules/@types/cacheable-request/node_modules/@types/node": {
"version": "22.10.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz",
"integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/@types/cacheable-request/node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"license": "MIT"
},
"node_modules/@types/color": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.6.tgz",
"integrity": "sha512-NMiNcZFRUAiUUCCf7zkAelY8eV3aKqfbzyFQlXpPIEeoNDbsEHGpb854V3gzTsGKYj830I5zPuOwU/TP5/cW6A==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@types/color/-/color-4.2.0.tgz",
"integrity": "sha512-6+xrIRImMtGAL2X3qYkd02Mgs+gFGs+WsK0b7VVMaO4mYRISwyTjcqNrO0mNSmYEoq++rSLDB2F5HDNmqfOe+A==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -11418,12 +11366,6 @@
"devOptional": true,
"license": "MIT"
},
"node_modules/@types/http-cache-semantics": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
"integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==",
"license": "MIT"
},
"node_modules/@types/http-errors": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
@ -11558,30 +11500,6 @@
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"license": "MIT"
},
"node_modules/@types/keyv": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
"integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/keyv/node_modules/@types/node": {
"version": "22.10.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz",
"integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/@types/keyv/node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"license": "MIT"
},
"node_modules/@types/lodash": {
"version": "4.17.7",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz",
@ -11826,30 +11744,6 @@
"@types/react": "^18.0.0"
}
},
"node_modules/@types/responselike": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
"integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/responselike/node_modules/@types/node": {
"version": "22.10.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz",
"integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/@types/responselike/node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"license": "MIT"
},
"node_modules/@types/retry": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz",
@ -14422,33 +14316,6 @@
"node": ">= 18"
}
},
"node_modules/cacheable-lookup": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
"integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
"license": "MIT",
"engines": {
"node": ">=10.6.0"
}
},
"node_modules/cacheable-request": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
"integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
"license": "MIT",
"dependencies": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
"http-cache-semantics": "^4.0.0",
"keyv": "^4.0.0",
"lowercase-keys": "^2.0.0",
"normalize-url": "^6.0.1",
"responselike": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/cachedir": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz",
@ -15058,27 +14925,6 @@
"node": ">=0.10.0"
}
},
"node_modules/clone-response": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
"integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
"license": "MIT",
"dependencies": {
"mimic-response": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/clone-response/node_modules/mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
@ -16881,21 +16727,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"license": "MIT",
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/dedent": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz",
@ -16984,15 +16815,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/defer-to-connect": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
"integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@ -17531,6 +17353,7 @@
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
@ -17584,21 +17407,21 @@
}
},
"node_modules/envalid": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/envalid/-/envalid-7.3.1.tgz",
"integrity": "sha512-KL1YRwn8WcoF/Ty7t+yLLtZol01xr9ZJMTjzoGRM8NaSU+nQQjSWOQKKJhJP2P57bpdakJ9jbxqQX4fGTOicZg==",
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/envalid/-/envalid-8.0.0.tgz",
"integrity": "sha512-PGeYJnJB5naN0ME6SH8nFcDj9HVbLpYIfg1p5lAyM9T4cH2lwtu2fLbozC/bq+HUUOIFxhX/LP0/GmlqPHT4tQ==",
"license": "MIT",
"dependencies": {
"tslib": "2.3.1"
"tslib": "2.6.2"
},
"engines": {
"node": ">=8.12"
}
},
"node_modules/envalid/node_modules/tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
"license": "0BSD"
},
"node_modules/environment": {
@ -19943,6 +19766,7 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"dev": true,
"license": "MIT",
"dependencies": {
"pump": "^3.0.0"
@ -20266,31 +20090,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/got": {
"version": "11.8.6",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
"integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
"license": "MIT",
"dependencies": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
"cacheable-request": "^7.0.2",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
"p-cancelable": "^2.0.0",
"responselike": "^2.0.0"
},
"engines": {
"node": ">=10.19.0"
},
"funding": {
"url": "https://github.com/sindresorhus/got?sponsor=1"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@ -20786,6 +20585,7 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/http-deceiver": {
@ -20937,19 +20737,6 @@
"integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==",
"license": "MIT"
},
"node_modules/http2-wrapper": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
"integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
"license": "MIT",
"dependencies": {
"quick-lru": "^5.1.1",
"resolve-alpn": "^1.0.0"
},
"engines": {
"node": ">=10.19.0"
}
},
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
@ -23281,6 +23068,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"dev": true,
"license": "MIT"
},
"node_modules/json-parse-even-better-errors": {
@ -23521,6 +23309,7 @@
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"dev": true,
"license": "MIT",
"dependencies": {
"json-buffer": "3.0.1"
@ -24746,15 +24535,6 @@
"devOptional": true,
"license": "0BSD"
},
"node_modules/lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
@ -25600,18 +25380,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/min-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
@ -26361,18 +26129,6 @@
"node": ">=0.10.0"
}
},
"node_modules/normalize-url": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/npm-bundled": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz",
@ -26888,6 +26644,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"devOptional": true,
"license": "ISC",
"dependencies": {
"wrappy": "1"
@ -27036,15 +26793,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/p-cancelable": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
"integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@ -28662,6 +28410,7 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
"integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
"dev": true,
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
@ -28759,18 +28508,6 @@
],
"license": "MIT"
},
"node_modules/quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/rambda": {
"version": "9.4.1",
"resolved": "https://registry.npmjs.org/rambda/-/rambda-9.4.1.tgz",
@ -29254,54 +28991,71 @@
}
},
"node_modules/replace-in-file": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-7.0.1.tgz",
"integrity": "sha512-KbhgPq04eA+TxXuUxpgWIH9k/TjF+28ofon2PXP7vq6izAILhxOtksCVcLuuQLtyjouBaPdlH6RJYYcSPVxCOA==",
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-8.3.0.tgz",
"integrity": "sha512-4VhddQiMCPIuypiwHDTM+XHjZoVu9h7ngBbSCnwGRcwdHwxltjt/m//Ep3GDwqaOx1fDSrKFQ+n7uo4uVcEz9Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"chalk": "^4.1.2",
"glob": "^8.1.0",
"chalk": "^5.3.0",
"glob": "^10.4.2",
"yargs": "^17.7.2"
},
"bin": {
"replace-in-file": "bin/cli.js"
},
"engines": {
"node": ">=10"
"node": ">=18"
}
},
"node_modules/replace-in-file/node_modules/chalk": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/replace-in-file/node_modules/glob": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
"integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^5.0.1",
"once": "^1.3.0"
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"engines": {
"node": ">=12"
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/replace-in-file/node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/request-progress": {
@ -29368,12 +29122,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve-alpn": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
"integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
"license": "MIT"
},
"node_modules/resolve-cwd": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
@ -29443,18 +29191,6 @@
"node": ">=10"
}
},
"node_modules/responselike": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
"integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
"license": "MIT",
"dependencies": {
"lowercase-keys": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
@ -34053,6 +33789,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"devOptional": true,
"license": "ISC"
},
"node_modules/write-file-atomic": {

11
package.json

@ -1,6 +1,6 @@
{
"name": "ghostfolio",
"version": "2.132.0",
"version": "2.133.1",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio",
@ -39,7 +39,7 @@
"postinstall": "prisma generate",
"prepare": "husky",
"prisma": "prisma",
"replace-placeholders-in-build": "node ./replace.build.js",
"replace-placeholders-in-build": "node ./replace.build.mjs",
"start": "node dist/apps/api/main",
"start:client": "nx run client:copy-assets && nx run client:serve --configuration=development-en --hmr -o",
"start:production": "npm run database:migrate && npm run database:seed && node main",
@ -109,9 +109,8 @@
"countries-list": "3.1.1",
"countup.js": "2.8.0",
"date-fns": "3.6.0",
"envalid": "7.3.1",
"envalid": "8.0.0",
"google-spreadsheet": "3.2.0",
"got": "11.8.6",
"helmet": "7.0.0",
"http-status-codes": "2.3.0",
"ionicons": "7.4.0",
@ -173,7 +172,7 @@
"@trivago/prettier-plugin-sort-imports": "4.3.0",
"@types/big.js": "6.2.2",
"@types/cache-manager": "4.0.6",
"@types/color": "3.0.6",
"@types/color": "4.2.0",
"@types/google-spreadsheet": "3.1.5",
"@types/jest": "29.5.13",
"@types/lodash": "4.17.7",
@ -199,7 +198,7 @@
"prisma": "6.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"replace-in-file": "7.0.1",
"replace-in-file": "8.3.0",
"shx": "0.3.4",
"storybook": "8.4.7",
"ts-jest": "29.1.0",

14
replace.build.js → replace.build.mjs

@ -1,9 +1,13 @@
const dotenv = require('dotenv');
const path = require('path');
const replace = require('replace-in-file');
import dotenv from 'dotenv';
import { dirname, resolve } from 'path';
import { replaceInFileSync } from 'replace-in-file';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
dotenv.config({
path: path.resolve(__dirname, '.env')
path: resolve(__dirname, '.env')
});
const now = new Date();
@ -16,7 +20,7 @@ const buildTimestamp = `${formatWithTwoDigits(
)}:${formatWithTwoDigits(now.getMinutes())}`;
try {
const changedFiles = replace.sync({
const changedFiles = replaceInFileSync({
files: './dist/apps/client/main.*.js',
from: /{BUILD_TIMESTAMP}/g,
to: buildTimestamp,
Loading…
Cancel
Save