Browse Source

Merge branch 'main' into es-translations

pull/5331/head
Thomas Kaul 2 weeks ago
committed by GitHub
parent
commit
1be8580171
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 4
      apps/api/src/app/endpoints/tags/create-tag.dto.ts
  3. 7
      apps/api/src/app/import/import-data.dto.ts
  4. 1
      apps/api/src/app/import/import.controller.ts
  5. 2
      apps/api/src/app/import/import.module.ts
  6. 96
      apps/api/src/app/import/import.service.ts
  7. 10
      apps/api/src/app/order/create-order.dto.ts
  8. 1
      apps/api/src/app/order/interfaces/activities.interface.ts
  9. 6
      apps/api/src/app/order/order.controller.ts
  10. 12
      apps/api/src/app/order/order.service.ts
  11. 10
      apps/api/src/app/order/update-order.dto.ts
  12. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts
  13. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts
  14. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  15. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts
  16. 4
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  17. 9
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
  18. 26
      apps/client/src/app/services/import-activities.service.ts
  19. 30
      apps/client/src/locales/messages.pl.xlf
  20. 33
      libs/common/src/lib/personal-finance-tools.ts
  21. 1
      libs/ui/src/lib/i18n.ts

2
CHANGELOG.md

@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Extended the import functionality by tags
- Improved the language localization for Polish (`pl`)
- Improved the language localization for Spanish (`es`)
## 2.189.0 - 2025-08-05

4
apps/api/src/app/endpoints/tags/create-tag.dto.ts

@ -1,6 +1,10 @@
import { IsOptional, IsString } from 'class-validator';
export class CreateTagDto {
@IsOptional()
@IsString()
id?: string;
@IsString()
name: string;

7
apps/api/src/app/import/import-data.dto.ts

@ -3,6 +3,7 @@ import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { Type } from 'class-transformer';
import { IsArray, IsOptional, ValidateNested } from 'class-validator';
import { CreateTagDto } from '../endpoints/tags/create-tag.dto';
import { CreateAccountWithBalancesDto } from './create-account-with-balances.dto';
import { CreateAssetProfileWithMarketDataDto } from './create-asset-profile-with-market-data.dto';
@ -23,4 +24,10 @@ export class ImportDataDto {
@Type(() => CreateAssetProfileWithMarketDataDto)
@ValidateNested({ each: true })
assetProfiles?: CreateAssetProfileWithMarketDataDto[];
@IsArray()
@IsOptional()
@Type(() => CreateTagDto)
@ValidateNested({ each: true })
tags?: CreateTagDto[];
}

1
apps/api/src/app/import/import.controller.ts

@ -74,6 +74,7 @@ export class ImportController {
accountsWithBalancesDto: importData.accounts ?? [],
activitiesDto: importData.activities,
assetProfilesWithMarketDataDto: importData.assetProfiles ?? [],
tagsDto: importData.tags ?? [],
user: this.request.user
});

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

@ -13,6 +13,7 @@ import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-da
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { TagModule } from '@ghostfolio/api/services/tag/tag.module';
import { Module } from '@nestjs/common';
@ -35,6 +36,7 @@ import { ImportService } from './import.service';
PrismaModule,
RedisCacheModule,
SymbolProfileModule,
TagModule,
TransformDataSourceInRequestModule,
TransformDataSourceInResponseModule
],

96
apps/api/src/app/import/import.service.ts

@ -13,12 +13,14 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
import { DATA_GATHERING_QUEUE_PRIORITY_HIGH } from '@ghostfolio/common/config';
import {
getAssetProfileIdentifier,
parseDate
} from '@ghostfolio/common/helper';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import {
AccountWithPlatform,
OrderWithAccount,
@ -46,7 +48,8 @@ export class ImportService {
private readonly orderService: OrderService,
private readonly platformService: PlatformService,
private readonly portfolioService: PortfolioService,
private readonly symbolProfileService: SymbolProfileService
private readonly symbolProfileService: SymbolProfileService,
private readonly tagService: TagService
) {}
public async getDividends({
@ -154,6 +157,7 @@ export class ImportService {
assetProfilesWithMarketDataDto,
isDryRun = false,
maxActivitiesToImport,
tagsDto,
user
}: {
accountsWithBalancesDto: ImportDataDto['accounts'];
@ -161,10 +165,12 @@ export class ImportService {
assetProfilesWithMarketDataDto: ImportDataDto['assetProfiles'];
isDryRun?: boolean;
maxActivitiesToImport: number;
tagsDto: ImportDataDto['tags'];
user: UserWithSettings;
}): Promise<Activity[]> {
const accountIdMapping: { [oldAccountId: string]: string } = {};
const assetProfileSymbolMapping: { [oldSymbol: string]: string } = {};
const tagIdMapping: { [oldTagId: string]: string } = {};
const userCurrency = user.settings.settings.baseCurrency;
if (!isDryRun && accountsWithBalancesDto?.length) {
@ -293,6 +299,50 @@ export class ImportService {
}
}
if (tagsDto?.length) {
const existingTagsOfUser = await this.tagService.getTagsForUser(user.id);
const canCreateOwnTag = hasPermission(
user.permissions,
permissions.createOwnTag
);
for (const tag of tagsDto) {
const existingTagOfUser = existingTagsOfUser.find(({ id }) => {
return id === tag.id;
});
if (!existingTagOfUser || existingTagOfUser.userId !== null) {
if (!canCreateOwnTag) {
throw new Error(
`Insufficient permissions to create custom tag ("${tag.name}")`
);
}
if (!isDryRun) {
const existingTag = await this.tagService.getTag({ id: tag.id });
let oldTagId: string;
if (existingTag) {
oldTagId = tag.id;
delete tag.id;
}
const tagObject: Prisma.TagCreateInput = {
...tag,
user: { connect: { id: user.id } }
};
const newTag = await this.tagService.createTag(tagObject);
if (existingTag && oldTagId) {
tagIdMapping[oldTagId] = newTag.id;
}
}
}
}
}
for (const activity of activitiesDto) {
if (!activity.dataSource) {
if (['FEE', 'INTEREST', 'LIABILITY'].includes(activity.type)) {
@ -313,6 +363,11 @@ export class ImportService {
if (assetProfileSymbolMapping[activity.symbol]) {
activity.symbol = assetProfileSymbolMapping[activity.symbol];
}
// If a new tag is created, then update the tag ID in all activities
activity.tags = (activity.tags ?? []).map((tagId) => {
return tagIdMapping[tagId] ?? tagId;
});
}
}
@ -340,6 +395,24 @@ export class ImportService {
});
}
const tags = (await this.tagService.getTagsForUser(user.id)).map(
({ id, name }) => {
return { id, name };
}
);
if (isDryRun) {
tagsDto
.filter(({ id }) => {
return !tags.some(({ id: tagId }) => {
return tagId === id;
});
})
.forEach(({ id, name }) => {
tags.push({ id, name });
});
}
const activities: Activity[] = [];
for (const activity of activitiesExtendedWithErrors) {
@ -351,6 +424,7 @@ export class ImportService {
const fee = activity.fee;
const quantity = activity.quantity;
const SymbolProfile = activity.SymbolProfile;
const tagIds = activity.tagIds ?? [];
const type = activity.type;
const unitPrice = activity.unitPrice;
@ -388,11 +462,17 @@ export class ImportService {
const validatedAccount = accounts.find(({ id }) => {
return id === accountId;
});
const validatedTags = tags.filter(({ id: tagId }) => {
return tagIds.some((activityTagId) => {
return activityTagId === tagId;
});
});
let order:
| OrderWithAccount
| (Omit<OrderWithAccount, 'Account'> & {
Account?: { id: string; name: string };
| (Omit<OrderWithAccount, 'account' | 'tags'> & {
account?: { id: string; name: string };
tags?: { id: string; name: string }[];
});
if (isDryRun) {
@ -404,7 +484,7 @@ export class ImportService {
quantity,
type,
unitPrice,
Account: validatedAccount,
account: validatedAccount,
accountId: validatedAccount?.id,
accountUserId: undefined,
createdAt: new Date(),
@ -436,6 +516,7 @@ export class ImportService {
userId: dataSource === 'MANUAL' ? user.id : undefined
},
symbolProfileId: undefined,
tags: validatedTags,
updatedAt: new Date(),
userId: user.id
};
@ -469,6 +550,9 @@ export class ImportService {
}
}
},
tags: validatedTags.map(({ id }) => {
return { id };
}),
updateAccountBalance: false,
user: { connect: { id: user.id } },
userId: user.id
@ -546,6 +630,7 @@ export class ImportService {
fee,
quantity,
symbol,
tags,
type,
unitPrice
}) => {
@ -594,7 +679,8 @@ export class ImportService {
isActive: true,
sectors: undefined,
updatedAt: undefined
}
},
tagIds: tags
};
}
);

10
apps/api/src/app/order/create-order.dto.ts

@ -1,13 +1,7 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970';
import {
AssetClass,
AssetSubClass,
DataSource,
Tag,
Type
} from '@prisma/client';
import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client';
import { Transform, TransformFnParams } from 'class-transformer';
import {
IsArray,
@ -70,7 +64,7 @@ export class CreateOrderDto {
@IsArray()
@IsOptional()
tags?: Tag[];
tags?: string[];
@IsEnum(Type, { each: true })
type: Type;

1
apps/api/src/app/order/interfaces/activities.interface.ts

@ -14,6 +14,7 @@ export interface Activity extends Order {
feeInAssetProfileCurrency: number;
feeInBaseCurrency: number;
SymbolProfile?: EnhancedSymbolProfile;
tagIds?: string[];
tags?: Tag[];
unitPriceInAssetProfileCurrency: number;
updateAccountBalance?: boolean;

6
apps/api/src/app/order/order.controller.ts

@ -217,6 +217,9 @@ export class OrderController {
}
}
},
tags: data.tags?.map((id) => {
return { id };
}),
user: { connect: { id: this.request.user.id } },
userId: this.request.user.id
});
@ -293,6 +296,9 @@ export class OrderController {
name: data.symbol
}
},
tags: data.tags?.map((id) => {
return { id };
}),
user: { connect: { id: this.request.user.id } }
},
where: {

12
apps/api/src/app/order/order.service.ts

@ -97,7 +97,7 @@ export class OrderService {
assetSubClass?: AssetSubClass;
currency?: string;
symbol?: string;
tags?: Tag[];
tags?: { id: string }[];
updateAccountBalance?: boolean;
userId: string;
}
@ -201,9 +201,7 @@ export class OrderService {
account,
isDraft,
tags: {
connect: tags.map(({ id }) => {
return { id };
})
connect: tags
}
},
include: { SymbolProfile: true }
@ -658,7 +656,7 @@ export class OrderService {
assetSubClass?: AssetSubClass;
currency?: string;
symbol?: string;
tags?: Tag[];
tags?: { id: string }[];
type?: ActivityType;
};
where: Prisma.OrderWhereUniqueInput;
@ -720,9 +718,7 @@ export class OrderService {
...data,
isDraft,
tags: {
connect: tags.map(({ id }) => {
return { id };
})
connect: tags
}
}
});

10
apps/api/src/app/order/update-order.dto.ts

@ -1,13 +1,7 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970';
import {
AssetClass,
AssetSubClass,
DataSource,
Tag,
Type
} from '@prisma/client';
import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client';
import { Transform, TransformFnParams } from 'class-transformer';
import {
IsArray,
@ -71,7 +65,7 @@ export class UpdateOrderDto {
@IsArray()
@IsOptional()
tags?: Tag[];
tags?: string[];
@IsString()
type: Type;

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts

@ -18,6 +18,7 @@ import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/po
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Tag } from '@prisma/client';
import { Big } from 'big.js';
import { join } from 'path';
@ -108,6 +109,9 @@ describe('PortfolioCalculator', () => {
name: 'Bitcoin',
symbol: activity.symbol
},
tags: activity.tags?.map((id) => {
return { id } as Tag;
}),
unitPriceInAssetProfileCurrency: 44558.42
}));

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts

@ -18,6 +18,7 @@ import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/po
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Tag } from '@prisma/client';
import { Big } from 'big.js';
import { join } from 'path';
@ -108,6 +109,9 @@ describe('PortfolioCalculator', () => {
name: 'Bitcoin',
symbol: activity.symbol
},
tags: activity.tags?.map((id) => {
return { id } as Tag;
}),
unitPriceInAssetProfileCurrency: 44558.42
}));

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts

@ -18,6 +18,7 @@ import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/po
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Tag } from '@prisma/client';
import { Big } from 'big.js';
import { join } from 'path';
@ -111,6 +112,9 @@ describe('PortfolioCalculator', () => {
name: 'Novartis AG',
symbol: activity.symbol
},
tags: activity.tags?.map((id) => {
return { id } as Tag;
}),
unitPriceInAssetProfileCurrency: activity.unitPrice
}));

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts

@ -18,6 +18,7 @@ import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/po
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Tag } from '@prisma/client';
import { Big } from 'big.js';
import { join } from 'path';
@ -111,6 +112,9 @@ describe('PortfolioCalculator', () => {
name: 'Novartis AG',
symbol: activity.symbol
},
tags: activity.tags?.map((id) => {
return { id } as Tag;
}),
unitPriceInAssetProfileCurrency: activity.unitPrice
}));

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

@ -495,7 +495,9 @@ export class GfCreateOrUpdateActivityDialog implements OnDestroy {
? undefined
: this.activityForm.get('searchSymbol')?.value?.symbol) ??
this.activityForm.get('name')?.value,
tags: this.activityForm.get('tags').value,
tags: this.activityForm.get('tags').value?.map(({ id }) => {
return id;
}),
type: this.activityForm.get('type').value,
unitPrice: this.activityForm.get('unitPrice').value
};

9
apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts

@ -1,3 +1,4 @@
import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto';
import { CreateAccountWithBalancesDto } from '@ghostfolio/api/app/import/create-account-with-balances.dto';
import { CreateAssetProfileWithMarketDataDto } from '@ghostfolio/api/app/import/create-asset-profile-with-market-data.dto';
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
@ -94,6 +95,7 @@ export class GfImportActivitiesDialog implements OnDestroy {
public sortColumn = 'date';
public sortDirection: SortDirection = 'desc';
public stepperOrientation: StepperOrientation;
public tags: CreateTagDto[] = [];
public totalItems: number;
private unsubscribeSubject = new Subject<void>();
@ -169,7 +171,8 @@ export class GfImportActivitiesDialog implements OnDestroy {
await this.importActivitiesService.importSelectedActivities({
accounts: this.accounts,
activities: this.selectedActivities,
assetProfiles: this.assetProfiles
assetProfiles: this.assetProfiles,
tags: this.tags
});
this.snackBar.open(
@ -297,6 +300,7 @@ export class GfImportActivitiesDialog implements OnDestroy {
this.accounts = content.accounts;
this.assetProfiles = content.assetProfiles;
this.tags = content.tags;
if (!isArray(content.activities)) {
if (isArray(content.orders)) {
@ -328,7 +332,8 @@ export class GfImportActivitiesDialog implements OnDestroy {
accounts: content.accounts,
activities: content.activities,
assetProfiles: content.assetProfiles,
isDryRun: true
isDryRun: true,
tags: content.tags
});
this.activities = activities;
this.dataSource = new MatTableDataSource(activities.reverse());

26
apps/client/src/app/services/import-activities.service.ts

@ -1,3 +1,4 @@
import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto';
import { CreateAccountWithBalancesDto } from '@ghostfolio/api/app/import/create-account-with-balances.dto';
import { CreateAssetProfileWithMarketDataDto } from '@ghostfolio/api/app/import/create-asset-profile-with-market-data.dto';
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
@ -75,12 +76,14 @@ export class ImportActivitiesService {
accounts,
activities,
assetProfiles,
isDryRun = false
isDryRun = false,
tags
}: {
activities: CreateOrderDto[];
accounts?: CreateAccountWithBalancesDto[];
assetProfiles?: CreateAssetProfileWithMarketDataDto[];
isDryRun?: boolean;
tags?: CreateTagDto[];
}): Promise<{
activities: Activity[];
}> {
@ -89,7 +92,8 @@ export class ImportActivitiesService {
{
accounts,
activities,
assetProfiles
assetProfiles,
tags
},
isDryRun
)
@ -110,11 +114,13 @@ export class ImportActivitiesService {
public importSelectedActivities({
accounts,
activities,
assetProfiles
assetProfiles,
tags
}: {
accounts?: CreateAccountWithBalancesDto[];
activities: Activity[];
assetProfiles?: CreateAssetProfileWithMarketDataDto[];
tags?: CreateTagDto[];
}): Promise<{
activities: Activity[];
}> {
@ -124,7 +130,12 @@ export class ImportActivitiesService {
importData.push(this.convertToCreateOrderDto(activity));
}
return this.importJson({ accounts, assetProfiles, activities: importData });
return this.importJson({
accounts,
assetProfiles,
tags,
activities: importData
});
}
private convertToCreateOrderDto({
@ -135,6 +146,7 @@ export class ImportActivitiesService {
fee,
quantity,
SymbolProfile,
tags,
type,
unitPrice,
updateAccountBalance
@ -150,7 +162,10 @@ export class ImportActivitiesService {
currency: currency ?? SymbolProfile.currency,
dataSource: SymbolProfile.dataSource,
date: date.toString(),
symbol: SymbolProfile.symbol
symbol: SymbolProfile.symbol,
tags: tags?.map(({ id }) => {
return id;
})
};
}
@ -391,6 +406,7 @@ export class ImportActivitiesService {
accounts?: CreateAccountWithBalancesDto[];
activities: CreateOrderDto[];
assetProfiles?: CreateAssetProfileWithMarketDataDto[];
tags?: CreateTagDto[];
},
aIsDryRun = false
) {

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

@ -3684,7 +3684,7 @@
</trans-unit>
<trans-unit id="79310201207169632" datatype="html">
<source>Exclude from Analysis</source>
<target state="new">Exclude from Analysis</target>
<target state="translated">Wyklucz z analizy</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html</context>
<context context-type="linenumber">90</context>
@ -7480,7 +7480,7 @@
</trans-unit>
<trans-unit id="2813275520458225294" datatype="html">
<source> Fuel your <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>self-hosted Ghostfolio<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> with a <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>powerful data provider<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> to access <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>80,000+ tickers<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> from over <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>50 exchanges<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> worldwide. </source>
<target state="new"> Fuel your <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>self-hosted Ghostfolio<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> with a <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>powerful data provider<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> to access <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>80,000+ tickers<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> from over <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>50 exchanges<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> worldwide. </target>
<target state="translated">Zasilać swój <x id="START_TAG_STRONG" ctype="x‑strong" equiv-text="&lt;strong&gt;"/>Ghostfolio self‑hosted<x id="CLOSE_TAG_STRONG" ctype="x‑strong" equiv-text="&lt;/strong&gt;"/> za pomocą potężnego <x id="START_TAG_STRONG" ctype="x‑strong" equiv-text="&lt;strong&gt;"/>dostawcy danych<x id="CLOSE_TAG_STRONG" ctype="x‑strong" equiv-text="&lt;/strong&gt;"/>, aby uzyskać dostęp do ponad <x id="START_TAG_STRONG" ctype="x‑strong" equiv-text="&lt;strong&gt;"/>80,000 notowań<x id="CLOSE_TAG_STRONG" ctype="x‑strong" equiv-text="&lt;/strong&gt;"/> z ponad <x id="START_TAG_STRONG" ctype="x‑strong" equiv-text="&lt;strong&gt;"/>50 giełd<x id="CLOSE_TAG_STRONG" ctype="x‑strong" equiv-text="&lt;/strong&gt;"/> na całym świecie.</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-settings/admin-settings.component.html</context>
<context context-type="linenumber">16</context>
@ -7556,7 +7556,7 @@
</trans-unit>
<trans-unit id="rule.accountClusterRiskCurrentInvestment.false" datatype="html">
<source> Over ${thresholdMax}% of your current investment is at ${maxAccountName} (${maxInvestmentRatio}%) </source>
<target state="new"> Over ${thresholdMax}% of your current investment is at ${maxAccountName} (${maxInvestmentRatio}%) </target>
<target state="translated">Ponad ${thresholdMax}% twojej bieżącej inwestycji znajduje się na koncie ${maxAccountName} (${maxInvestmentRatio}%)</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">17</context>
@ -7564,7 +7564,7 @@
</trans-unit>
<trans-unit id="rule.accountClusterRiskCurrentInvestment.true" datatype="html">
<source> The major part of your current investment is at ${maxAccountName} (${maxInvestmentRatio}%) and does not exceed ${thresholdMax}% </source>
<target state="new"> The major part of your current investment is at ${maxAccountName} (${maxInvestmentRatio}%) and does not exceed ${thresholdMax}% </target>
<target state="translated">Główna część twojej obecnej inwestycji znajduje się na koncie ${maxAccountName} (${maxInvestmentRatio}%) i nie przekracza ${thresholdMax}%</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">24</context>
@ -7572,7 +7572,7 @@
</trans-unit>
<trans-unit id="rule.assetClassClusterRiskEquity" datatype="html">
<source>Equity</source>
<target state="new">Equity</target>
<target state="translated">Kapitał własny</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">41</context>
@ -7580,7 +7580,7 @@
</trans-unit>
<trans-unit id="rule.assetClassClusterRiskEquity.false.max" datatype="html">
<source> The equity contribution of your current investment (${equityValueRatio}%) exceeds ${thresholdMax}% </source>
<target state="new"> The equity contribution of your current investment (${equityValueRatio}%) exceeds ${thresholdMax}% </target>
<target state="translated">Udział kapitału własnego w twojej obecnej inwestycji (${equityValueRatio}%) przekracza ${thresholdMax}%</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">43</context>
@ -7588,7 +7588,7 @@
</trans-unit>
<trans-unit id="rule.assetClassClusterRiskEquity.false.min" datatype="html">
<source> The equity contribution of your current investment (${equityValueRatio}%) is below ${thresholdMin}% </source>
<target state="new"> The equity contribution of your current investment (${equityValueRatio}%) is below ${thresholdMin}% </target>
<target state="translated">Udział kapitału własnego w twojej obecnej inwestycji (${equityValueRatio}%) jest poniżej ${thresholdMin}%</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">47</context>
@ -7596,7 +7596,7 @@
</trans-unit>
<trans-unit id="rule.assetClassClusterRiskEquity.true" datatype="html">
<source> The equity contribution of your current investment (${equityValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% </source>
<target state="new"> The equity contribution of your current investment (${equityValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% </target>
<target state="translated">Udział kapitału własnego w twojej obecnej inwestycji (${equityValueRatio}%) mieści się w zakresie od ${thresholdMin}% do ${thresholdMax}%</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">51</context>
@ -7604,7 +7604,7 @@
</trans-unit>
<trans-unit id="rule.assetClassClusterRiskFixedIncome" datatype="html">
<source>Fixed Income</source>
<target state="new">Fixed Income</target>
<target state="translated">Dochód stały</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">55</context>
@ -7612,7 +7612,7 @@
</trans-unit>
<trans-unit id="rule.assetClassClusterRiskFixedIncome.false.max" datatype="html">
<source> The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) exceeds ${thresholdMax}% </source>
<target state="new"> The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) exceeds ${thresholdMax}% </target>
<target state="translated">Udział dochodu stałego w twojej obecnej inwestycji (${fixedIncomeValueRatio}%) przekracza ${thresholdMax}%</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">57</context>
@ -7620,7 +7620,7 @@
</trans-unit>
<trans-unit id="rule.assetClassClusterRiskFixedIncome.false.min" datatype="html">
<source> The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) is below ${thresholdMin}% </source>
<target state="new"> The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) is below ${thresholdMin}% </target>
<target state="translated">Udział dochodu stałego w twojej obecnej inwestycji (${fixedIncomeValueRatio}%) jest poniżej ${thresholdMin}%</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">61</context>
@ -7628,7 +7628,7 @@
</trans-unit>
<trans-unit id="rule.assetClassClusterRiskFixedIncome.true" datatype="html">
<source> The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% </source>
<target state="new"> The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% </target>
<target state="translated">Udział dochodu stałego w twojej obecnej inwestycji (${fixedIncomeValueRatio}%) mieści się w zakresie od ${thresholdMin}% do ${thresholdMax}%</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">66</context>
@ -7636,7 +7636,7 @@
</trans-unit>
<trans-unit id="rule.currencyClusterRiskBaseCurrencyCurrentInvestment" datatype="html">
<source> Investment: Base Currency </source>
<target state="new"> Investment: Base Currency </target>
<target state="translated">Inwestycja: Waluta bazowa</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">72</context>
@ -7644,7 +7644,7 @@
</trans-unit>
<trans-unit id="rule.currencyClusterRiskBaseCurrencyCurrentInvestment.false" datatype="html">
<source> The major part of your current investment is not in your base currency (${baseCurrencyValueRatio}% in ${baseCurrency}) </source>
<target state="new"> The major part of your current investment is not in your base currency (${baseCurrencyValueRatio}% in ${baseCurrency}) </target>
<target state="translated">Główna część twojej bieżącej inwestycji nie jest w walucie bazowej (${baseCurrencyValueRatio}% w ${baseCurrency})</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">75</context>
@ -7652,7 +7652,7 @@
</trans-unit>
<trans-unit id="rule.currencyClusterRiskBaseCurrencyCurrentInvestment.true" datatype="html">
<source> The major part of your current investment is in your base currency (${baseCurrencyValueRatio}% in ${baseCurrency}) </source>
<target state="new"> The major part of your current investment is in your base currency (${baseCurrencyValueRatio}% in ${baseCurrency}) </target>
<target state="translated">Główna część twojej bieżącej inwestycji jest w walucie bazowej (${baseCurrencyValueRatio}% w ${baseCurrency})</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">79</context>

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

@ -107,7 +107,7 @@ export const personalFinanceTools: Product[] = [
key: 'capitally',
name: 'Capitally',
origin: 'Poland',
pricingPerYear: '€50',
pricingPerYear: '€80',
slogan: 'Optimize your investments performance'
},
{
@ -163,7 +163,7 @@ export const personalFinanceTools: Product[] = [
key: 'copilot-money',
name: 'Copilot Money',
origin: 'United States',
pricingPerYear: '$70',
pricingPerYear: '$95',
slogan: 'Do money better with Copilot'
},
{
@ -364,6 +364,24 @@ export const personalFinanceTools: Product[] = [
pricingPerYear: '$65',
slogan: 'Take control over your investments'
},
{
hasFreePlan: true,
hasSelfHostingAbility: false,
key: 'gasti',
languages: [
'Deutsch',
'English',
'Español',
'Français',
'Italiano',
'Português'
],
name: 'Gasti',
origin: 'Argentina',
pricingPerYear: '$60',
regions: ['Global'],
slogan: 'Take control of your finances from WhatsApp'
},
{
founded: 2020,
hasFreePlan: true,
@ -450,7 +468,7 @@ export const personalFinanceTools: Product[] = [
key: 'kubera',
name: 'Kubera®',
origin: 'United States',
pricingPerYear: '$150',
pricingPerYear: '$249',
slogan: 'The Time Machine for your Net Worth'
},
{
@ -493,7 +511,10 @@ export const personalFinanceTools: Product[] = [
},
{
founded: 2021,
hasFreePlan: false,
hasSelfHostingAbility: true,
isArchived: true,
isOpenSource: true,
key: 'maybe-finance',
languages: ['English'],
name: 'Maybe Finance',
@ -609,7 +630,7 @@ export const personalFinanceTools: Product[] = [
name: 'Parqet',
note: 'Originally named as Tresor One',
origin: 'Germany',
pricingPerYear: '€88',
pricingPerYear: '€99.99',
regions: ['Austria', 'Germany', 'Switzerland'],
slogan: 'Dein Vermögen immer im Blick'
},
@ -989,7 +1010,7 @@ export const personalFinanceTools: Product[] = [
key: 'ynab',
name: 'YNAB (You Need a Budget)',
origin: 'United States',
pricingPerYear: '$99',
pricingPerYear: '$109',
slogan: 'Change Your Relationship With Money'
},
{
@ -999,7 +1020,7 @@ export const personalFinanceTools: Product[] = [
key: 'ziggma',
name: 'Ziggma',
origin: 'United States',
pricingPerYear: '$90',
pricingPerYear: '$84',
slogan: 'Your solution for investing success'
}
];

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

@ -74,6 +74,7 @@ const locales = {
// Countries
Armenia: $localize`Armenia`,
Argentina: $localize`Argentina`,
Australia: $localize`Australia`,
Austria: $localize`Austria`,
Belgium: $localize`Belgium`,

Loading…
Cancel
Save