Browse Source

Merge branch 'main' into task/filter-mcp-tools-by-scopes-of-access

pull/7935/head
Thomas Kaul 2 days ago
committed by GitHub
parent
commit
5edb199058
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      CHANGELOG.md
  2. 2
      apps/api/src/app/auth-device/auth-device.controller.ts
  3. 2
      apps/api/src/app/endpoints/asset-profiles/asset-profiles.controller.ts
  4. 2
      apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts
  5. 2
      apps/api/src/app/endpoints/market-data/market-data.controller.ts
  6. 2
      apps/api/src/app/endpoints/tags/tags.controller.ts
  7. 2
      apps/api/src/app/endpoints/watchlist/watchlist.controller.ts
  8. 198
      apps/api/src/app/portfolio/portfolio.service.spec.ts
  9. 119
      apps/api/src/app/portfolio/portfolio.service.ts
  10. 38
      apps/api/src/assets/cryptocurrencies/cryptocurrencies.json
  11. 20
      apps/client/src/locales/messages.ca.xlf
  12. 1
      libs/common/src/lib/interfaces/product.interface.ts
  13. 1137
      libs/common/src/lib/personal-finance-tools.ts
  14. 1
      libs/common/src/lib/types/product-category.type.ts
  15. 1
      libs/ui/src/lib/i18n.ts

6
CHANGELOG.md

@ -10,10 +10,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Improved the server of the Model Context Protocol (MCP) to list only the tools covered by the scopes of the access (experimental) - Improved the server of the Model Context Protocol (MCP) to list only the tools covered by the scopes of the access (experimental)
- Refreshed the cryptocurrencies list
- Improved the language localization for Catalan (`ca`)
- Upgraded `@rekog/mcp-nest` from version `2.0.2` to `2.0.7` - Upgraded `@rekog/mcp-nest` from version `2.0.2` to `2.0.7`
- Upgraded `bull-board` from version `9.9.0` to `9.10.1` - Upgraded `bull-board` from version `9.9.0` to `9.10.1`
- Upgraded `zod` from version `4.5.4` to `4.6.5` - Upgraded `zod` from version `4.5.4` to `4.6.5`
### Fixed
- Fixed the value of the holdings excluded from analysis in the portfolio summary
## 3.72.0 - 2026-09-20 ## 3.72.0 - 2026-09-20
### Added ### Added

2
apps/api/src/app/auth-device/auth-device.controller.ts

@ -3,7 +3,7 @@ import { AllowDuringImpersonation } from '@ghostfolio/api/decorators/allow-durin
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { permissions } from '@ghostfolio/common/permissions'; import { permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types'; import type { RequestWithUser } from '@ghostfolio/common/types';
import { import {
Controller, Controller,

2
apps/api/src/app/endpoints/asset-profiles/asset-profiles.controller.ts

@ -17,7 +17,7 @@ import {
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { hasPermission } from '@ghostfolio/common/permissions'; import { hasPermission } from '@ghostfolio/common/permissions';
import { permissions } from '@ghostfolio/common/permissions'; import { permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types'; import type { RequestWithUser } from '@ghostfolio/common/types';
import { import {
Body, Body,

2
apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts

@ -12,7 +12,7 @@ import {
QuotesResponse QuotesResponse
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions'; import { permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types'; import type { RequestWithUser } from '@ghostfolio/common/types';
import { import {
Controller, Controller,

2
apps/api/src/app/endpoints/market-data/market-data.controller.ts

@ -8,7 +8,7 @@ import { UpdateBulkMarketDataDto } from '@ghostfolio/common/dtos';
import { getCurrencyFromSymbol, isCurrency } from '@ghostfolio/common/helper'; import { getCurrencyFromSymbol, isCurrency } from '@ghostfolio/common/helper';
import { MarketDataOfMarketsResponse } from '@ghostfolio/common/interfaces'; import { MarketDataOfMarketsResponse } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types'; import type { RequestWithUser } from '@ghostfolio/common/types';
import { import {
Body, Body,

2
apps/api/src/app/endpoints/tags/tags.controller.ts

@ -5,7 +5,7 @@ import { TagService } from '@ghostfolio/api/services/tag/tag.service';
import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos'; import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos';
import { isSystemTag } from '@ghostfolio/common/helper'; import { isSystemTag } from '@ghostfolio/common/helper';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types'; import type { RequestWithUser } from '@ghostfolio/common/types';
import { import {
Body, Body,

2
apps/api/src/app/endpoints/watchlist/watchlist.controller.ts

@ -7,7 +7,7 @@ import { CreateWatchlistItemDto } from '@ghostfolio/common/dtos';
import { WatchlistResponse } from '@ghostfolio/common/interfaces'; import { WatchlistResponse } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions'; import { permissions } from '@ghostfolio/common/permissions';
import { scopes } from '@ghostfolio/common/scopes'; import { scopes } from '@ghostfolio/common/scopes';
import { ImpersonationContext } from '@ghostfolio/common/types'; import type { ImpersonationContext } from '@ghostfolio/common/types';
import { import {
Body, Body,

198
apps/api/src/app/portfolio/portfolio.service.spec.ts

@ -773,6 +773,27 @@ describe('PortfolioService', () => {
).getSummary(args); ).getSummary(args);
}; };
function createExcludedActivity({
currency = 'CHF',
quantity,
symbol,
type,
unitPrice
}: Pick<Activity, 'quantity' | 'type' | 'unitPrice'> & {
currency?: string;
symbol: string;
}) {
return {
currency,
quantity,
type,
unitPrice,
account: { tags: [{ id: TAG_ID_EXCLUDE_FROM_ANALYSIS }] },
assetProfile: { currency, symbol, dataSource: DataSource.YAHOO },
tags: []
} as unknown as Activity;
}
function createPortfolioCalculator() { function createPortfolioCalculator() {
return { return {
getDividendInBaseCurrency: jest.fn().mockResolvedValue(new Big(0)), getDividendInBaseCurrency: jest.fn().mockResolvedValue(new Big(0)),
@ -791,9 +812,13 @@ describe('PortfolioService', () => {
beforeEach(() => { beforeEach(() => {
jest jest
.spyOn(activitiesService, 'getActivities') .spyOn(activitiesService, 'getActivitiesForPortfolioCalculator')
.mockResolvedValue({ activities: [], count: 0 }); .mockResolvedValue({ activities: [], count: 0 });
jest
.spyOn(exchangeRateDataService, 'toCurrency')
.mockImplementation((aValue) => aValue);
jest.spyOn(portfolioService, 'getPerformance').mockResolvedValue({ jest.spyOn(portfolioService, 'getPerformance').mockResolvedValue({
performance: { performance: {
currentValueInBaseCurrency: 3000, currentValueInBaseCurrency: 3000,
@ -837,6 +862,177 @@ describe('PortfolioService', () => {
expect(summary.totalAssetsInBaseCurrency).toBe(3000); expect(summary.totalAssetsInBaseCurrency).toBe(3000);
expect(summary.totalValueInBaseCurrency).toBe(3000); expect(summary.totalValueInBaseCurrency).toBe(3000);
}); });
it('should value the open holdings of excluded accounts at the current market price', async () => {
jest.spyOn(accountService, 'getCashDetails').mockResolvedValue({
accounts: [],
balanceInBaseCurrency: 1500
});
jest
.spyOn(activitiesService, 'getActivitiesForPortfolioCalculator')
.mockResolvedValue({
activities: [
createExcludedActivity({
quantity: 10,
symbol: 'AAPL',
type: 'BUY',
unitPrice: 100
}),
createExcludedActivity({
quantity: 4,
symbol: 'AAPL',
type: 'SELL',
unitPrice: 150
}),
createExcludedActivity({
quantity: 5,
symbol: 'MSFT',
type: 'BUY',
unitPrice: 100
}),
createExcludedActivity({
quantity: 5,
symbol: 'MSFT',
type: 'SELL',
unitPrice: 120
})
],
count: 4
});
const getQuotes = jest
.spyOn(dataProviderService, 'getQuotes')
.mockResolvedValue({
'YAHOO-AAPL': {
currency: 'CHF',
dataSource: DataSource.YAHOO,
marketPrice: 200,
marketState: 'open'
}
});
const summary = await getSummary({
balanceInBaseCurrency: 1000,
emergencyFundHoldingsValueInBaseCurrency: 0,
filteredValueInBaseCurrency: new Big(3000),
portfolioCalculator: createPortfolioCalculator(),
userCurrency: 'CHF',
userId: userDummyData.id
});
// The closed holding (MSFT) does not need a quote
expect(getQuotes).toHaveBeenCalledWith(
expect.objectContaining({
items: [{ dataSource: DataSource.YAHOO, symbol: 'AAPL' }]
})
);
// 500 (balance of excluded accounts) + 6 * 200 (AAPL) + 0 (MSFT)
expect(summary.excludedAccountsAndActivities).toBe(1700);
expect(summary.totalValueInBaseCurrency).toBe(4700);
});
it('should convert the market value of an excluded holding from the currency of the asset profile to the user currency', async () => {
jest.spyOn(accountService, 'getCashDetails').mockResolvedValue({
accounts: [],
balanceInBaseCurrency: 1000
});
jest
.spyOn(activitiesService, 'getActivitiesForPortfolioCalculator')
.mockResolvedValue({
activities: [
createExcludedActivity({
currency: 'USD',
quantity: 10,
symbol: 'AAPL',
type: 'BUY',
unitPrice: 100
}),
createExcludedActivity({
currency: 'USD',
quantity: 4,
symbol: 'AAPL',
type: 'SELL',
unitPrice: 150
})
],
count: 2
});
jest.spyOn(dataProviderService, 'getQuotes').mockResolvedValue({
'YAHOO-AAPL': {
currency: 'USD',
dataSource: DataSource.YAHOO,
marketPrice: 200,
marketState: 'open'
}
});
jest
.spyOn(exchangeRateDataService, 'toCurrency')
.mockImplementation((aValue, aFromCurrency, aToCurrency) => {
return aFromCurrency === 'USD' && aToCurrency === 'CHF'
? aValue * 0.8
: aValue;
});
const summary = await getSummary({
balanceInBaseCurrency: 1000,
emergencyFundHoldingsValueInBaseCurrency: 0,
filteredValueInBaseCurrency: new Big(3000),
portfolioCalculator: createPortfolioCalculator(),
userCurrency: 'CHF',
userId: userDummyData.id
});
// 6 * 200 USD (AAPL) * 0.8 (USDCHF)
expect(summary.excludedAccountsAndActivities).toBe(960);
expect(summary.totalValueInBaseCurrency).toBe(3960);
});
it('should fall back to the unit price of the latest activity of an excluded holding without a market price', async () => {
jest.spyOn(accountService, 'getCashDetails').mockResolvedValue({
accounts: [],
balanceInBaseCurrency: 1000
});
jest
.spyOn(activitiesService, 'getActivitiesForPortfolioCalculator')
.mockResolvedValue({
activities: [
createExcludedActivity({
quantity: 10,
symbol: 'AAPL',
type: 'BUY',
unitPrice: 100
}),
createExcludedActivity({
quantity: 4,
symbol: 'AAPL',
type: 'SELL',
unitPrice: 150
})
],
count: 2
});
jest.spyOn(dataProviderService, 'getQuotes').mockResolvedValue({});
const summary = await getSummary({
balanceInBaseCurrency: 1000,
emergencyFundHoldingsValueInBaseCurrency: 0,
filteredValueInBaseCurrency: new Big(3000),
portfolioCalculator: createPortfolioCalculator(),
userCurrency: 'CHF',
userId: userDummyData.id
});
// 6 * 150 (unit price of the latest activity)
expect(summary.excludedAccountsAndActivities).toBe(900);
expect(summary.totalValueInBaseCurrency).toBe(3900);
});
}); });
describe('getValueOfAccountsAndPlatforms', () => { describe('getValueOfAccountsAndPlatforms', () => {

119
apps/api/src/app/portfolio/portfolio.service.ts

@ -39,6 +39,7 @@ import {
TAG_ID_EXCLUDE_FROM_ANALYSIS, TAG_ID_EXCLUDE_FROM_ANALYSIS,
UNKNOWN_KEY UNKNOWN_KEY
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { SubscriptionType } from '@ghostfolio/common/enums';
import { import {
DATE_FORMAT, DATE_FORMAT,
getAssetProfileIdentifier, getAssetProfileIdentifier,
@ -2093,7 +2094,8 @@ export class PortfolioService {
}): Promise<PortfolioSummary> { }): Promise<PortfolioSummary> {
const user = await this.userService.user({ id: userId }); const user = await this.userService.user({ id: userId });
const { activities } = await this.activitiesService.getActivities({ const { activities } =
await this.activitiesService.getActivitiesForPortfolioCalculator({
userCurrency, userCurrency,
userId, userId,
withExcludedAccountsAndActivities: true withExcludedAccountsAndActivities: true
@ -2164,31 +2166,28 @@ export class PortfolioService {
.plus(emergencyFundHoldingsValueInBaseCurrency) .plus(emergencyFundHoldingsValueInBaseCurrency)
.toNumber(); .toNumber();
const totalOfExcludedActivities = this.getSumOfActivityType({ const [
userCurrency, cashDetailsWithExcludedAccounts,
activities: excludedActivities, valueOfExcludedActivitiesInBaseCurrency
activityType: 'BUY' ] = await Promise.all([
}).minus( this.accountService.getCashDetails({
this.getSumOfActivityType({
userCurrency,
activities: excludedActivities,
activityType: 'SELL'
})
);
const cashDetailsWithExcludedAccounts =
await this.accountService.getCashDetails({
userId, userId,
currency: userCurrency, currency: userCurrency,
withExcludedAccounts: true withExcludedAccounts: true
}); }),
this.getValueOfExcludedActivitiesInBaseCurrency({
userCurrency,
activities: excludedActivities,
subscriptionType: user.subscription?.type
})
]);
const excludedBalanceInBaseCurrency = new Big( const excludedBalanceInBaseCurrency = new Big(
cashDetailsWithExcludedAccounts.balanceInBaseCurrency cashDetailsWithExcludedAccounts.balanceInBaseCurrency
).minus(balanceInBaseCurrency); ).minus(balanceInBaseCurrency);
const excludedAccountsAndActivities = excludedBalanceInBaseCurrency const excludedAccountsAndActivities = excludedBalanceInBaseCurrency
.plus(totalOfExcludedActivities) .plus(valueOfExcludedActivitiesInBaseCurrency)
.toNumber(); .toNumber();
// Exclude emergency fund from the financial independence calculation // Exclude emergency fund from the financial independence calculation
@ -2491,6 +2490,92 @@ export class PortfolioService {
return { accounts, platforms }; return { accounts, platforms };
} }
private async getValueOfExcludedActivitiesInBaseCurrency({
activities,
subscriptionType,
userCurrency
}: {
activities: Activity[];
subscriptionType?: SubscriptionType;
userCurrency: string;
}) {
const holdings: {
[assetProfileIdentifier: string]: {
latestActivity: Activity;
quantity: Big;
};
} = {};
// The activities are sorted by date in ascending order
for (const activity of activities) {
const factor = getFactor(activity.type);
if (factor === 0 || isDraftActivity(activity)) {
continue;
}
const assetProfileIdentifier = getAssetProfileIdentifier(
activity.assetProfile
);
const quantity = holdings[assetProfileIdentifier]?.quantity ?? new Big(0);
holdings[assetProfileIdentifier] = {
latestActivity: activity,
quantity: quantity.plus(new Big(activity.quantity).mul(factor))
};
}
const openHoldings = Object.values(holdings).filter(({ quantity }) => {
return !quantity.eq(0);
});
const quotes =
openHoldings.length > 0
? await this.dataProviderService.getQuotes({
subscriptionType,
items: openHoldings.map(
({
latestActivity: {
assetProfile: { dataSource, symbol }
}
}) => {
return { dataSource, symbol };
}
)
})
: {};
return getSum(
openHoldings.map(({ latestActivity, quantity }) => {
const { assetProfile, currency, unitPrice } = latestActivity;
const marketPrice =
quotes[getAssetProfileIdentifier(assetProfile)]?.marketPrice;
if (!marketPrice) {
// Fall back to the unit price of the latest activity without a
// market price
return new Big(
this.exchangeRateDataService.toCurrency(
quantity.mul(unitPrice).toNumber(),
currency ?? assetProfile.currency,
userCurrency
)
);
}
return new Big(
this.exchangeRateDataService.toCurrency(
quantity.mul(marketPrice).toNumber(),
assetProfile.currency,
userCurrency
)
);
})
);
}
private isExcludedFromAnalysis(activity: Activity) { private isExcludedFromAnalysis(activity: Activity) {
return ( return (
isAccountExcluded(activity.account) || isAccountExcluded(activity.account) ||

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

@ -459,7 +459,7 @@
"AHARWBTC": "Aave Harmony WBTC", "AHARWBTC": "Aave Harmony WBTC",
"AHOO": "Ahoolee", "AHOO": "Ahoolee",
"AHT": "AhaToken", "AHT": "AhaToken",
"AI": "Flourishing AI", "AI": "Gensyn",
"AI16Z": "ai16z", "AI16Z": "ai16z",
"AI21X": "ai21x", "AI21X": "ai21x",
"AI23T": "23 Turtles", "AI23T": "23 Turtles",
@ -467,7 +467,7 @@
"AI3": "Autonomys Network", "AI3": "Autonomys Network",
"AI4": "AI⁴", "AI4": "AI⁴",
"AI69SAKURA": "Sakura", "AI69SAKURA": "Sakura",
"AIA": "AIA Chain", "AIA": "DeAgentAI",
"AIACHAIN": "AIA Chain", "AIACHAIN": "AIA Chain",
"AIAF": "AI Agent Factory", "AIAF": "AI Agent Factory",
"AIAGENT": "AI Agents", "AIAGENT": "AI Agents",
@ -1338,7 +1338,6 @@
"AUX": "Auxilium", "AUX": "Auxilium",
"AV": "AvatarCoin", "AV": "AvatarCoin",
"AVA": "AVA", "AVA": "AVA",
"AVA34326": "AVA Chiang Mai Night Safari",
"AVAAI": "Ava AI", "AVAAI": "Ava AI",
"AVACN": "AVACOIN", "AVACN": "AVACOIN",
"AVAI": "Orca AVAI", "AVAI": "Orca AVAI",
@ -2066,7 +2065,7 @@
"BHIGH": "Backed HIGH € High Yield Corp Bond", "BHIGH": "Backed HIGH € High Yield Corp Bond",
"BHIRE": "BitHIRE", "BHIRE": "BitHIRE",
"BHIVE": "Hive", "BHIVE": "Hive",
"BHO": "Bholdus", "BHO": "Bholdus Token",
"BHP": "Blockchain of Hash Power", "BHP": "Blockchain of Hash Power",
"BHPC": "BHPCash", "BHPC": "BHPCash",
"BIAFRA": "Biafra Coin", "BIAFRA": "Biafra Coin",
@ -3064,6 +3063,7 @@
"BTU": "BTU Protocol", "BTU": "BTU Protocol",
"BTV": "Bitvote", "BTV": "Bitvote",
"BTW": "BitWhite", "BTW": "BitWhite",
"BTW39158": "Bitway",
"BTX": "BitCore", "BTX": "BitCore",
"BTXC": "Bettex coin", "BTXC": "Bettex coin",
"BTXEX": "BTXEX", "BTXEX": "BTXEX",
@ -4055,7 +4055,6 @@
"COMAI": "Commune AI", "COMAI": "Commune AI",
"COMB": "Combine.finance", "COMB": "Combine.finance",
"COMBO": "COMBO", "COMBO": "COMBO",
"COMBO8259": "Furucombo",
"COMBOX": "ComBox", "COMBOX": "ComBox",
"COMC": "ComCrica Token", "COMC": "ComCrica Token",
"COME": "Community of Meme", "COME": "Community of Meme",
@ -4096,7 +4095,7 @@
"COOHA": "CoolHash", "COOHA": "CoolHash",
"COOK": "Cook Finance", "COOK": "Cook Finance",
"COOK33720": "mETH Protocol", "COOK33720": "mETH Protocol",
"COOKIE": "CookieSale", "COOKIE": "Cookie DAO",
"COOKTOKEN": "Cook", "COOKTOKEN": "Cook",
"COOL": "CoolCoin", "COOL": "CoolCoin",
"COOP": "Coop Network", "COOP": "Coop Network",
@ -8898,6 +8897,7 @@
"IMS": "Independent Money System", "IMS": "Independent Money System",
"IMST": "Imsmart", "IMST": "Imsmart",
"IMT": "Moneytoken", "IMT": "Moneytoken",
"IMU": "Immunefi",
"IMUSIFY": "imusify", "IMUSIFY": "imusify",
"IMVR": "ImmVRse", "IMVR": "ImmVRse",
"IMX": "Immutable", "IMX": "Immutable",
@ -9654,6 +9654,7 @@
"KINECOSYSTEM": "Kin", "KINECOSYSTEM": "Kin",
"KINET": "KinetixFi", "KINET": "KinetixFi",
"KING": "King Swap", "KING": "King Swap",
"KING33695": "King Protocol",
"KING93": "King93", "KING93": "King93",
"KINGB": "King Bean", "KINGB": "King Bean",
"KINGBONK": "King Bonk", "KINGBONK": "King Bonk",
@ -9981,7 +9982,6 @@
"LAELAPS": "Laelaps", "LAELAPS": "Laelaps",
"LAFFIN": "Laffin Kamala", "LAFFIN": "Laffin Kamala",
"LAI": "LayerAI", "LAI": "LayerAI",
"LAI23846": "LayerAI",
"LAIKA": "LAIKA", "LAIKA": "LAIKA",
"LAIKAPROTOCOL": "Laika Protocol", "LAIKAPROTOCOL": "Laika Protocol",
"LAINESOL": "Laine Staked SOL", "LAINESOL": "Laine Staked SOL",
@ -11576,7 +11576,8 @@
"MOMO": "Momo", "MOMO": "Momo",
"MOMO2": "MOMO 2.0", "MOMO2": "MOMO 2.0",
"MOMO2025": "momo", "MOMO2025": "momo",
"MON": "PocMon", "MON": "Monad",
"MON22337": "PocMon",
"MON30495": "Monad", "MON30495": "Monad",
"MON30950": "MON", "MON30950": "MON",
"MONA": "MonaCoin", "MONA": "MonaCoin",
@ -11645,7 +11646,6 @@
"MOOMEME": "MOO MOO", "MOOMEME": "MOO MOO",
"MOOMOO": "MOOMOO THE BULL", "MOOMOO": "MOOMOO THE BULL",
"MOON": "MoonSwap", "MOON": "MoonSwap",
"MOON7396": "r/CryptoCurrency Moons",
"MOONARCH": "Moonarch.app", "MOONARCH": "Moonarch.app",
"MOONB": "Moon Base", "MOONB": "Moon Base",
"MOONBEANS": "Moonbeans", "MOONBEANS": "Moonbeans",
@ -13604,7 +13604,8 @@
"PHV": "PATHHIVE", "PHV": "PATHHIVE",
"PHX": "Phoenix Global [old]", "PHX": "Phoenix Global [old]",
"PHY": "DePHY", "PHY": "DePHY",
"PI": "Plian", "PI": "Pi Network",
"PI2620": "Plian",
"PI35697": "Pi", "PI35697": "Pi",
"PIA": "Olympia AI", "PIA": "Olympia AI",
"PIAI": "Pi Network AI", "PIAI": "Pi Network AI",
@ -13830,7 +13831,7 @@
"PNDC": "Pond Coin", "PNDC": "Pond Coin",
"PNDN": "Pandana", "PNDN": "Pandana",
"PNDO": "Pondo", "PNDO": "Pondo",
"PNDR": "Pandora Protocol", "PNDR": "Pandora Finance",
"PNFT": "Pawn My NFT", "PNFT": "Pawn My NFT",
"PNG": "Pangolin", "PNG": "Pangolin",
"PNGDA": "Pengda Yellow Panda", "PNGDA": "Pengda Yellow Panda",
@ -14001,7 +14002,7 @@
"POWERMARKET": "POWER MARKET", "POWERMARKET": "POWER MARKET",
"POWR": "Powerledger", "POWR": "Powerledger",
"POWSCHE": "Powsche", "POWSCHE": "Powsche",
"POX": "Pollux Coin", "POX": "Monkey Pox",
"POZO": "Pozo Coin", "POZO": "Pozo Coin",
"PP": "Phoenix Protocol Dao", "PP": "Phoenix Protocol Dao",
"PPAD": "PlayPad", "PPAD": "PlayPad",
@ -14508,6 +14509,7 @@
"RAIL": "Railgun", "RAIL": "Railgun",
"RAILS": "Rails Token", "RAILS": "Rails Token",
"RAIN": "Rainmaker Games", "RAIN": "Rainmaker Games",
"RAIN38341": "Rain",
"RAINBOW": "Rainbow Token", "RAINBOW": "Rainbow Token",
"RAINBOWTOKEN": "Rainbow Token", "RAINBOWTOKEN": "Rainbow Token",
"RAINC": "RainCheck", "RAINC": "RainCheck",
@ -15930,7 +15932,8 @@
"SKULL": "Skull Order", "SKULL": "Skull Order",
"SKUY": "SEKUYA", "SKUY": "SEKUYA",
"SKX": "SKPANAX", "SKX": "SKPANAX",
"SKY": "Skycoin", "SKY": "Sky",
"SKY1619": "Skycoin",
"SKY33038": "Sky", "SKY33038": "Sky",
"SKYA": "Sekuya Multiverse", "SKYA": "Sekuya Multiverse",
"SKYAI": "SKYAI", "SKYAI": "SKYAI",
@ -18274,7 +18277,8 @@
"USDQ": "Quantoz USDQ", "USDQ": "Quantoz USDQ",
"USDQSTABLE": "USDQ", "USDQSTABLE": "USDQ",
"USDR": "Wrapped USDR", "USDR": "Wrapped USDR",
"USDS": "Stably Classic", "USDS": "USDS",
"USDS20039": "USDS",
"USDS33039": "USDS", "USDS33039": "USDS",
"USDSB": "USDSB", "USDSB": "USDSB",
"USDSTABLY": "StableUSD", "USDSTABLY": "StableUSD",
@ -18698,7 +18702,7 @@
"VPAY": "VPay by Virtuals", "VPAY": "VPay by Virtuals",
"VPK": "Vulture Peak", "VPK": "Vulture Peak",
"VPND": "VaporNodes", "VPND": "VaporNodes",
"VPP": "Virtue Poker", "VPP": "Virtue Poker Points",
"VPR": "VaporWallet", "VPR": "VaporWallet",
"VPRC": "VapersCoin", "VPRC": "VapersCoin",
"VPS": "VPS AI", "VPS": "VPS AI",
@ -18948,7 +18952,7 @@
"WDOT": "WDOT", "WDOT": "WDOT",
"WDR": "Wider Coin", "WDR": "Wider Coin",
"WDX": "WeiDex", "WDX": "WeiDex",
"WE": "Wanda Exchange", "WE": "WeBuy",
"WEALTH": "WealthCoin", "WEALTH": "WealthCoin",
"WEAPON": "MEGAWEAPON", "WEAPON": "MEGAWEAPON",
"WEAR": "MetaWear", "WEAR": "MetaWear",
@ -19116,7 +19120,6 @@
"WINB": "WINBIT CASINO", "WINB": "WINBIT CASINO",
"WINE": "Wine Shares", "WINE": "Wine Shares",
"WING": "Wing Finance", "WING": "Wing Finance",
"WING7048": "Wing Finance",
"WINGS": "Wings", "WINGS": "Wings",
"WINK": "Wink", "WINK": "Wink",
"WINN": "Winnerz", "WINN": "Winnerz",
@ -19687,7 +19690,6 @@
"XPLA": "XPLA", "XPLA": "XPLA",
"XPLL": "ParallelChain", "XPLL": "ParallelChain",
"XPM": "Primecoin", "XPM": "Primecoin",
"XPM20261": "XPMarket",
"XPN": "PANTHEON X", "XPN": "PANTHEON X",
"XPND": "Time Raiders", "XPND": "Time Raiders",
"XPNET": "XP NETWORK", "XPNET": "XP NETWORK",

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

@ -3,7 +3,7 @@
<body> <body>
<trans-unit id="659291745893337969" datatype="html"> <trans-unit id="659291745893337969" datatype="html">
<source>Invalid API key</source> <source>Invalid API key</source>
<target state="new">Invalid API key</target> <target state="translated">Clau API no vàlida</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-settings/admin-settings.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-settings/admin-settings.component.html</context>
<context context-type="linenumber">115</context> <context context-type="linenumber">115</context>
@ -516,7 +516,7 @@
</trans-unit> </trans-unit>
<trans-unit id="4323470180912194028" datatype="html"> <trans-unit id="4323470180912194028" datatype="html">
<source>Copy</source> <source>Copy</source>
<target state="new">Copy</target> <target state="translated">Copia</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html</context>
<context context-type="linenumber">20</context> <context context-type="linenumber">20</context>
@ -788,7 +788,7 @@
</trans-unit> </trans-unit>
<trans-unit id="4467323362722952678" datatype="html"> <trans-unit id="4467323362722952678" datatype="html">
<source>Unknown</source> <source>Unknown</source>
<target state="new">Unknown</target> <target state="translated">Desconegut</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context> <context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
<context context-type="linenumber">89</context> <context context-type="linenumber">89</context>
@ -904,7 +904,7 @@
</trans-unit> </trans-unit>
<trans-unit id="3973560112150137298" datatype="html"> <trans-unit id="3973560112150137298" datatype="html">
<source>Find an account...</source> <source>Find an account...</source>
<target state="new">Find an account...</target> <target state="translated">Cerca un compte...</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/assistant/assistant.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/assistant/assistant.component.ts</context>
<context context-type="linenumber">450</context> <context context-type="linenumber">450</context>
@ -984,7 +984,7 @@
</trans-unit> </trans-unit>
<trans-unit id="1806667489382256324" datatype="html"> <trans-unit id="1806667489382256324" datatype="html">
<source>Category</source> <source>Category</source>
<target state="new">Category</target> <target state="translated">Categoria</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/resources/personal-finance-tools/product-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/resources/personal-finance-tools/product-page.html</context>
<context context-type="linenumber">76</context> <context context-type="linenumber">76</context>
@ -1644,7 +1644,7 @@
</trans-unit> </trans-unit>
<trans-unit id="4340477809050781416" datatype="html"> <trans-unit id="4340477809050781416" datatype="html">
<source>Current year</source> <source>Current year</source>
<target state="new">Current year</target> <target state="translated">Any actual</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts</context>
<context context-type="linenumber">267</context> <context context-type="linenumber">267</context>
@ -2080,7 +2080,7 @@
</trans-unit> </trans-unit>
<trans-unit id="1022809987579838138" datatype="html"> <trans-unit id="1022809987579838138" datatype="html">
<source>Last Used</source> <source>Last Used</source>
<target state="new">Last Used</target> <target state="translated">Últim ús</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/access-table/access-table.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/access-table/access-table.component.html</context>
<context context-type="linenumber">31</context> <context context-type="linenumber">31</context>
@ -2184,7 +2184,7 @@
</trans-unit> </trans-unit>
<trans-unit id="6004588582437169024" datatype="html"> <trans-unit id="6004588582437169024" datatype="html">
<source>Current week</source> <source>Current week</source>
<target state="new">Current week</target> <target state="translated">Setmana actual</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts</context>
<context context-type="linenumber">259</context> <context context-type="linenumber">259</context>
@ -2992,7 +2992,7 @@
</trans-unit> </trans-unit>
<trans-unit id="3885497195825665706" datatype="html"> <trans-unit id="3885497195825665706" datatype="html">
<source>Next</source> <source>Next</source>
<target state="new">Next</target> <target state="translated">Següent</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/resources/personal-finance-tools/product-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/resources/personal-finance-tools/product-page.html</context>
<context context-type="linenumber">406</context> <context context-type="linenumber">406</context>
@ -3148,7 +3148,7 @@
</trans-unit> </trans-unit>
<trans-unit id="2988589012964101797" datatype="html"> <trans-unit id="2988589012964101797" datatype="html">
<source>Daily</source> <source>Daily</source>
<target state="new">Daily</target> <target state="translated">Diàriament</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts</context>
<context context-type="linenumber">247</context> <context context-type="linenumber">247</context>

1
libs/common/src/lib/interfaces/product.interface.ts

@ -17,6 +17,7 @@ export interface Product {
pricingPerYear?: string; pricingPerYear?: string;
regions?: string[]; regions?: string[];
slogan?: string; slogan?: string;
updatedAt?: string;
url?: string; url?: string;
useAnonymously?: boolean; useAnonymously?: boolean;
} }

1137
libs/common/src/lib/personal-finance-tools.ts

File diff suppressed because it is too large

1
libs/common/src/lib/types/product-category.type.ts

@ -4,6 +4,7 @@ export type ProductCategory =
| 'DIVIDEND_TRACKING' | 'DIVIDEND_TRACKING'
| 'ETF_TRACKING' | 'ETF_TRACKING'
| 'FAMILY_OFFICE' | 'FAMILY_OFFICE'
| 'FINANCIAL_ADVISOR'
| 'FINANCIAL_PLANNING' | 'FINANCIAL_PLANNING'
| 'INVESTMENT_RESEARCH' | 'INVESTMENT_RESEARCH'
| 'NET_WORTH_TRACKING' | 'NET_WORTH_TRACKING'

1
libs/ui/src/lib/i18n.ts

@ -107,6 +107,7 @@ const locales = {
DIVIDEND_TRACKING: $localize`Dividend Tracking`, DIVIDEND_TRACKING: $localize`Dividend Tracking`,
ETF_TRACKING: $localize`ETF Tracking`, ETF_TRACKING: $localize`ETF Tracking`,
FAMILY_OFFICE: $localize`Family Office`, FAMILY_OFFICE: $localize`Family Office`,
FINANCIAL_ADVISOR: $localize`Financial Advisor`,
FINANCIAL_PLANNING: $localize`Financial Planning`, FINANCIAL_PLANNING: $localize`Financial Planning`,
INVESTMENT_RESEARCH: $localize`Investment Research`, INVESTMENT_RESEARCH: $localize`Investment Research`,
NET_WORTH_TRACKING: $localize`Net Worth Tracking`, NET_WORTH_TRACKING: $localize`Net Worth Tracking`,

Loading…
Cancel
Save