Browse Source

Feature/localize X-ray rule FeeRatioInitialInvestment (#4779)

* Localize X-ray rule FeeRatioInitialInvestment

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
pull/4835/head
Tobias Kugel 4 weeks ago
committed by GitHub
parent
commit
fde8ff4bb6
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      CHANGELOG.md
  2. 14
      apps/api/src/app/auth/jwt.strategy.ts
  3. 10
      apps/api/src/app/endpoints/ai/ai.controller.ts
  4. 1
      apps/api/src/app/portfolio/portfolio.service.ts
  5. 2
      apps/api/src/app/subscription/subscription.service.ts
  6. 1
      apps/api/src/app/user/user.service.ts
  7. 15
      apps/api/src/models/rule.ts
  8. 7
      apps/api/src/models/rules/account-cluster-risk/current-investment.ts
  9. 7
      apps/api/src/models/rules/account-cluster-risk/single-account.ts
  10. 7
      apps/api/src/models/rules/asset-class-cluster-risk/equity.ts
  11. 7
      apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts
  12. 7
      apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts
  13. 7
      apps/api/src/models/rules/currency-cluster-risk/current-investment.ts
  14. 7
      apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts
  15. 7
      apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts
  16. 7
      apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts
  17. 36
      apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts
  18. 7
      apps/api/src/models/rules/regional-market-cluster-risk/asia-pacific.ts
  19. 7
      apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts
  20. 7
      apps/api/src/models/rules/regional-market-cluster-risk/europe.ts
  21. 7
      apps/api/src/models/rules/regional-market-cluster-risk/japan.ts
  22. 7
      apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts
  23. 12
      apps/api/src/services/i18n/i18n.service.ts
  24. 3
      apps/client/src/app/components/access-table/access-table.component.ts
  25. 10
      apps/client/src/app/components/admin-settings/admin-settings.component.ts
  26. 9
      apps/client/src/app/pages/i18n/i18n-page.html

4
CHANGELOG.md

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Set up the language localization for the static portfolio analysis rule: _Fees_ (Fee Ratio)
### Changed
- Upgraded `Nx` from version `20.8.1` to `21.1.2`

14
apps/api/src/app/auth/jwt.strategy.ts

@ -1,7 +1,11 @@
import { UserService } from '@ghostfolio/api/app/user/user.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { HEADER_KEY_TIMEZONE } from '@ghostfolio/common/config';
import {
DEFAULT_CURRENCY,
DEFAULT_LANGUAGE_CODE,
HEADER_KEY_TIMEZONE
} from '@ghostfolio/common/config';
import { hasRole } from '@ghostfolio/common/permissions';
import { HttpException, Injectable } from '@nestjs/common';
@ -52,6 +56,14 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
});
}
if (!user.Settings.settings.baseCurrency) {
user.Settings.settings.baseCurrency = DEFAULT_CURRENCY;
}
if (!user.Settings.settings.language) {
user.Settings.settings.language = DEFAULT_LANGUAGE_CODE;
}
return user;
} else {
throw new HttpException(

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

@ -1,10 +1,6 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import {
DEFAULT_CURRENCY,
DEFAULT_LANGUAGE_CODE
} from '@ghostfolio/common/config';
import { AiPromptResponse } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import type { AiPromptMode, RequestWithUser } from '@ghostfolio/common/types';
@ -53,10 +49,8 @@ export class AiController {
filters,
mode,
impersonationId: undefined,
languageCode:
this.request.user.Settings.settings.language ?? DEFAULT_LANGUAGE_CODE,
userCurrency:
this.request.user.Settings.settings.baseCurrency ?? DEFAULT_CURRENCY,
languageCode: this.request.user.Settings.settings.language,
userCurrency: this.request.user.Settings.settings.baseCurrency,
userId: this.request.user.id
});

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

@ -1332,6 +1332,7 @@ export class PortfolioService {
[
new FeeRatioInitialInvestment(
this.exchangeRateDataService,
userSettings.language,
summary.committedFunds,
summary.fees
)

2
apps/api/src/app/subscription/subscription.service.ts

@ -61,7 +61,7 @@ export class SubscriptionService {
const checkoutSessionCreateParams: Stripe.Checkout.SessionCreateParams = {
cancel_url: `${this.configurationService.get('ROOT_URL')}/${
user.Settings?.settings?.language ?? DEFAULT_LANGUAGE_CODE
user.Settings.settings.language
}/account`,
client_reference_id: user.id,
line_items: [

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

@ -302,6 +302,7 @@ export class UserService {
undefined
).getSettings(user.Settings.settings),
FeeRatioInitialInvestment: new FeeRatioInitialInvestment(
undefined,
undefined,
undefined,
undefined

15
apps/api/src/models/rule.ts

@ -1,5 +1,6 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config';
import { groupBy } from '@ghostfolio/common/helper';
import {
PortfolioPosition,
@ -14,28 +15,28 @@ import { RuleInterface } from './interfaces/rule.interface';
export abstract class Rule<T extends RuleSettings> implements RuleInterface<T> {
private key: string;
private name: string;
private languageCode: string;
public constructor(
protected exchangeRateDataService: ExchangeRateDataService,
{
key,
name
languageCode = DEFAULT_LANGUAGE_CODE
}: {
key: string;
name: string;
languageCode?: string; // TODO: Make mandatory
}
) {
this.key = key;
this.name = name;
this.languageCode = languageCode;
}
public getKey() {
return this.key;
}
public getName() {
return this.name;
public getLanguageCode() {
return this.languageCode;
}
public groupCurrentHoldingsByAttribute(
@ -73,5 +74,7 @@ export abstract class Rule<T extends RuleSettings> implements RuleInterface<T> {
PortfolioReportRule['configuration']
>;
public abstract getName(): string;
public abstract getSettings(aUserSettings: UserSettings): T;
}

7
apps/api/src/models/rules/account-cluster-risk/current-investment.ts

@ -15,8 +15,7 @@ export class AccountClusterRiskCurrentInvestment extends Rule<Settings> {
accounts: PortfolioDetails['accounts']
) {
super(exchangeRateDataService, {
key: AccountClusterRiskCurrentInvestment.name,
name: 'Investment'
key: AccountClusterRiskCurrentInvestment.name
});
this.accounts = accounts;
@ -88,6 +87,10 @@ export class AccountClusterRiskCurrentInvestment extends Rule<Settings> {
};
}
public getName() {
return 'Investment';
}
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings {
return {
baseCurrency,

7
apps/api/src/models/rules/account-cluster-risk/single-account.ts

@ -11,8 +11,7 @@ export class AccountClusterRiskSingleAccount extends Rule<RuleSettings> {
accounts: PortfolioDetails['accounts']
) {
super(exchangeRateDataService, {
key: AccountClusterRiskSingleAccount.name,
name: 'Single Account'
key: AccountClusterRiskSingleAccount.name
});
this.accounts = accounts;
@ -38,6 +37,10 @@ export class AccountClusterRiskSingleAccount extends Rule<RuleSettings> {
return undefined;
}
public getName() {
return 'Single Account';
}
public getSettings({ xRayRules }: UserSettings): RuleSettings {
return {
isActive: xRayRules?.[this.getKey()].isActive ?? true

7
apps/api/src/models/rules/asset-class-cluster-risk/equity.ts

@ -11,8 +11,7 @@ export class AssetClassClusterRiskEquity extends Rule<Settings> {
holdings: PortfolioPosition[]
) {
super(exchangeRateDataService, {
key: AssetClassClusterRiskEquity.name,
name: 'Equity'
key: AssetClassClusterRiskEquity.name
});
this.holdings = holdings;
@ -78,6 +77,10 @@ export class AssetClassClusterRiskEquity extends Rule<Settings> {
};
}
public getName() {
return 'Equity';
}
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings {
return {
baseCurrency,

7
apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts

@ -11,8 +11,7 @@ export class AssetClassClusterRiskFixedIncome extends Rule<Settings> {
holdings: PortfolioPosition[]
) {
super(exchangeRateDataService, {
key: AssetClassClusterRiskFixedIncome.name,
name: 'Fixed Income'
key: AssetClassClusterRiskFixedIncome.name
});
this.holdings = holdings;
@ -78,6 +77,10 @@ export class AssetClassClusterRiskFixedIncome extends Rule<Settings> {
};
}
public getName() {
return 'Fixed Income';
}
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings {
return {
baseCurrency,

7
apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts

@ -11,8 +11,7 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Setti
holdings: PortfolioPosition[]
) {
super(exchangeRateDataService, {
key: CurrencyClusterRiskBaseCurrencyCurrentInvestment.name,
name: 'Investment: Base Currency'
key: CurrencyClusterRiskBaseCurrencyCurrentInvestment.name
});
this.holdings = holdings;
@ -68,6 +67,10 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Setti
return undefined;
}
public getName() {
return 'Investment: Base Currency';
}
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings {
return {
baseCurrency,

7
apps/api/src/models/rules/currency-cluster-risk/current-investment.ts

@ -11,8 +11,7 @@ export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
holdings: PortfolioPosition[]
) {
super(exchangeRateDataService, {
key: CurrencyClusterRiskCurrentInvestment.name,
name: 'Investment'
key: CurrencyClusterRiskCurrentInvestment.name
});
this.holdings = holdings;
@ -73,6 +72,10 @@ export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
};
}
public getName() {
return 'Investment';
}
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings {
return {
baseCurrency,

7
apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts

@ -13,8 +13,7 @@ export class EconomicMarketClusterRiskDevelopedMarkets extends Rule<Settings> {
developedMarketsValueInBaseCurrency: number
) {
super(exchangeRateDataService, {
key: EconomicMarketClusterRiskDevelopedMarkets.name,
name: 'Developed Markets'
key: EconomicMarketClusterRiskDevelopedMarkets.name
});
this.currentValueInBaseCurrency = currentValueInBaseCurrency;
@ -67,6 +66,10 @@ export class EconomicMarketClusterRiskDevelopedMarkets extends Rule<Settings> {
};
}
public getName() {
return 'Developed Markets';
}
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings {
return {
baseCurrency,

7
apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts

@ -13,8 +13,7 @@ export class EconomicMarketClusterRiskEmergingMarkets extends Rule<Settings> {
emergingMarketsValueInBaseCurrency: number
) {
super(exchangeRateDataService, {
key: EconomicMarketClusterRiskEmergingMarkets.name,
name: 'Emerging Markets'
key: EconomicMarketClusterRiskEmergingMarkets.name
});
this.currentValueInBaseCurrency = currentValueInBaseCurrency;
@ -67,6 +66,10 @@ export class EconomicMarketClusterRiskEmergingMarkets extends Rule<Settings> {
};
}
public getName() {
return 'Emerging Markets';
}
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings {
return {
baseCurrency,

7
apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts

@ -11,8 +11,7 @@ export class EmergencyFundSetup extends Rule<Settings> {
emergencyFund: number
) {
super(exchangeRateDataService, {
key: EmergencyFundSetup.name,
name: 'Emergency Fund: Set up'
key: EmergencyFundSetup.name
});
this.emergencyFund = emergencyFund;
@ -36,6 +35,10 @@ export class EmergencyFundSetup extends Rule<Settings> {
return undefined;
}
public getName() {
return 'Emergency Fund: Set up';
}
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings {
return {
baseCurrency,

36
apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts

@ -1,20 +1,23 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { UserSettings } from '@ghostfolio/common/interfaces';
export class FeeRatioInitialInvestment extends Rule<Settings> {
private fees: number;
private i18nService = new I18nService();
private totalInvestment: number;
public constructor(
protected exchangeRateDataService: ExchangeRateDataService,
languageCode: string,
totalInvestment: number,
fees: number
) {
super(exchangeRateDataService, {
key: FeeRatioInitialInvestment.name,
name: 'Fee Ratio'
languageCode,
key: FeeRatioInitialInvestment.name
});
this.fees = fees;
@ -28,17 +31,27 @@ export class FeeRatioInitialInvestment extends Rule<Settings> {
if (feeRatio > ruleSettings.thresholdMax) {
return {
evaluation: `The fees do exceed ${
ruleSettings.thresholdMax * 100
}% of your initial investment (${(feeRatio * 100).toPrecision(3)}%)`,
evaluation: this.i18nService.getTranslation({
id: 'rule.feeRatioInitialInvestment.false',
languageCode: this.getLanguageCode(),
placeholders: {
feeRatio: (ruleSettings.thresholdMax * 100).toFixed(2),
thresholdMax: (feeRatio * 100).toPrecision(3)
}
}),
value: false
};
}
return {
evaluation: `The fees do not exceed ${
ruleSettings.thresholdMax * 100
}% of your initial investment (${(feeRatio * 100).toPrecision(3)}%)`,
evaluation: this.i18nService.getTranslation({
id: 'rule.feeRatioInitialInvestment.true',
languageCode: this.getLanguageCode(),
placeholders: {
feeRatio: (feeRatio * 100).toPrecision(3),
thresholdMax: (ruleSettings.thresholdMax * 100).toFixed(2)
}
}),
value: true
};
}
@ -55,6 +68,13 @@ export class FeeRatioInitialInvestment extends Rule<Settings> {
};
}
public getName() {
return this.i18nService.getTranslation({
id: 'rule.feeRatioInitialInvestment',
languageCode: this.getLanguageCode()
});
}
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings {
return {
baseCurrency,

7
apps/api/src/models/rules/regional-market-cluster-risk/asia-pacific.ts

@ -14,8 +14,7 @@ export class RegionalMarketClusterRiskAsiaPacific extends Rule<Settings> {
asiaPacificValueInBaseCurrency: number
) {
super(exchangeRateDataService, {
key: RegionalMarketClusterRiskAsiaPacific.name,
name: 'Asia-Pacific'
key: RegionalMarketClusterRiskAsiaPacific.name
});
this.asiaPacificValueInBaseCurrency = asiaPacificValueInBaseCurrency;
@ -66,6 +65,10 @@ export class RegionalMarketClusterRiskAsiaPacific extends Rule<Settings> {
};
}
public getName() {
return 'Asia-Pacific';
}
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings {
return {
baseCurrency,

7
apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts

@ -14,8 +14,7 @@ export class RegionalMarketClusterRiskEmergingMarkets extends Rule<Settings> {
emergingMarketsValueInBaseCurrency: number
) {
super(exchangeRateDataService, {
key: RegionalMarketClusterRiskEmergingMarkets.name,
name: 'Emerging Markets'
key: RegionalMarketClusterRiskEmergingMarkets.name
});
this.currentValueInBaseCurrency = currentValueInBaseCurrency;
@ -68,6 +67,10 @@ export class RegionalMarketClusterRiskEmergingMarkets extends Rule<Settings> {
};
}
public getName() {
return 'Emerging Markets';
}
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings {
return {
baseCurrency,

7
apps/api/src/models/rules/regional-market-cluster-risk/europe.ts

@ -14,8 +14,7 @@ export class RegionalMarketClusterRiskEurope extends Rule<Settings> {
europeValueInBaseCurrency: number
) {
super(exchangeRateDataService, {
key: RegionalMarketClusterRiskEurope.name,
name: 'Europe'
key: RegionalMarketClusterRiskEurope.name
});
this.currentValueInBaseCurrency = currentValueInBaseCurrency;
@ -66,6 +65,10 @@ export class RegionalMarketClusterRiskEurope extends Rule<Settings> {
};
}
public getName() {
return 'Europe';
}
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings {
return {
baseCurrency,

7
apps/api/src/models/rules/regional-market-cluster-risk/japan.ts

@ -14,8 +14,7 @@ export class RegionalMarketClusterRiskJapan extends Rule<Settings> {
japanValueInBaseCurrency: number
) {
super(exchangeRateDataService, {
key: RegionalMarketClusterRiskJapan.name,
name: 'Japan'
key: RegionalMarketClusterRiskJapan.name
});
this.currentValueInBaseCurrency = currentValueInBaseCurrency;
@ -66,6 +65,10 @@ export class RegionalMarketClusterRiskJapan extends Rule<Settings> {
};
}
public getName() {
return 'Japan';
}
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings {
return {
baseCurrency,

7
apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts

@ -14,8 +14,7 @@ export class RegionalMarketClusterRiskNorthAmerica extends Rule<Settings> {
northAmericaValueInBaseCurrency: number
) {
super(exchangeRateDataService, {
key: RegionalMarketClusterRiskNorthAmerica.name,
name: 'North America'
key: RegionalMarketClusterRiskNorthAmerica.name
});
this.currentValueInBaseCurrency = currentValueInBaseCurrency;
@ -66,6 +65,10 @@ export class RegionalMarketClusterRiskNorthAmerica extends Rule<Settings> {
};
}
public getName() {
return 'North America';
}
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings {
return {
baseCurrency,

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

@ -15,10 +15,12 @@ export class I18nService {
public getTranslation({
id,
languageCode
languageCode,
placeholders
}: {
id: string;
languageCode: string;
placeholders?: Record<string, string | number>;
}): string {
const $ = this.translations[languageCode];
@ -26,7 +28,7 @@ export class I18nService {
Logger.warn(`Translation not found for locale '${languageCode}'`);
}
const translatedText = $(
let translatedText = $(
`trans-unit[id="${id}"] > ${
languageCode === DEFAULT_LANGUAGE_CODE ? 'source' : 'target'
}`
@ -38,6 +40,12 @@ export class I18nService {
);
}
if (placeholders) {
for (const [key, value] of Object.entries(placeholders)) {
translatedText = translatedText.replace(`{${key}}`, String(value));
}
}
return translatedText.trim();
}

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

@ -1,6 +1,5 @@
import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type';
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config';
import { Access, User } from '@ghostfolio/common/interfaces';
import { paths } from '@ghostfolio/common/paths';
@ -54,7 +53,7 @@ export class AccessTableComponent implements OnChanges {
}
public getPublicUrl(aId: string): string {
const languageCode = this.user?.settings?.language ?? DEFAULT_LANGUAGE_CODE;
const languageCode = this.user.settings.language;
return `${this.baseUrl}/${languageCode}/${paths.public}/${aId}`;
}

10
apps/client/src/app/components/admin-settings/admin-settings.component.ts

@ -3,10 +3,7 @@ import { NotificationService } from '@ghostfolio/client/core/notification/notifi
import { AdminService } from '@ghostfolio/client/services/admin.service';
import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import {
DEFAULT_LANGUAGE_CODE,
PROPERTY_API_KEY_GHOSTFOLIO
} from '@ghostfolio/common/config';
import { PROPERTY_API_KEY_GHOSTFOLIO } from '@ghostfolio/common/config';
import { getDateFormatString } from '@ghostfolio/common/helper';
import {
DataProviderGhostfolioStatusResponse,
@ -70,11 +67,10 @@ export class AdminSettingsComponent implements OnDestroy, OnInit {
this.user = state.user;
this.defaultDateFormat = getDateFormatString(
this.user?.settings?.locale
this.user.settings.locale
);
const languageCode =
this.user?.settings?.language ?? DEFAULT_LANGUAGE_CODE;
const languageCode = this.user.settings.language;
this.pricingUrl = `https://ghostfol.io/${languageCode}/${paths.pricing}`;

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

@ -11,6 +11,15 @@
performance, portfolio, software, stock, trading, wealth, web3
</li>
<li i18n="@@myAccount">My Account</li>
<li i18n="@@rule.feeRatioInitialInvestment">Fee Ratio</li>
<li i18n="@@rule.feeRatioInitialInvestment.false">
The fees do exceed &#123;thresholdMax&#125;% of your initial investment
(&#123;feeRatio&#125;%)
</li>
<li i18n="@@rule.feeRatioInitialInvestment.true">
The fees do not exceed &#123;thresholdMax&#125;% of your initial
investment (&#123;feeRatio&#125;%)
</li>
<li i18n="@@slogan">Open Source Wealth Management Software</li>
</ul>
</div>

Loading…
Cancel
Save