From fde8ff4bb6dba99b7b045bbb34bf90a685912e62 Mon Sep 17 00:00:00 2001 From: Tobias Kugel <78074722+tobikugel@users.noreply.github.com> Date: Thu, 5 Jun 2025 14:31:45 -0300 Subject: [PATCH] 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> --- CHANGELOG.md | 4 +++ apps/api/src/app/auth/jwt.strategy.ts | 14 +++++++- .../api/src/app/endpoints/ai/ai.controller.ts | 10 ++---- .../src/app/portfolio/portfolio.service.ts | 1 + .../app/subscription/subscription.service.ts | 2 +- apps/api/src/app/user/user.service.ts | 1 + apps/api/src/models/rule.ts | 15 ++++---- .../current-investment.ts | 7 ++-- .../account-cluster-risk/single-account.ts | 7 ++-- .../rules/asset-class-cluster-risk/equity.ts | 7 ++-- .../asset-class-cluster-risk/fixed-income.ts | 7 ++-- .../base-currency-current-investment.ts | 7 ++-- .../current-investment.ts | 7 ++-- .../developed-markets.ts | 7 ++-- .../emerging-markets.ts | 7 ++-- .../emergency-fund/emergency-fund-setup.ts | 7 ++-- .../fees/fee-ratio-initial-investment.ts | 36 ++++++++++++++----- .../asia-pacific.ts | 7 ++-- .../emerging-markets.ts | 7 ++-- .../regional-market-cluster-risk/europe.ts | 7 ++-- .../regional-market-cluster-risk/japan.ts | 7 ++-- .../north-america.ts | 7 ++-- apps/api/src/services/i18n/i18n.service.ts | 12 +++++-- .../access-table/access-table.component.ts | 3 +- .../admin-settings.component.ts | 10 ++---- apps/client/src/app/pages/i18n/i18n-page.html | 9 +++++ 26 files changed, 152 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63c2b12d1..68117a0ea 100644 --- a/CHANGELOG.md +++ b/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` diff --git a/apps/api/src/app/auth/jwt.strategy.ts b/apps/api/src/app/auth/jwt.strategy.ts index 7a3fb224b..3d466082a 100644 --- a/apps/api/src/app/auth/jwt.strategy.ts +++ b/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( diff --git a/apps/api/src/app/endpoints/ai/ai.controller.ts b/apps/api/src/app/endpoints/ai/ai.controller.ts index 980d5607c..8050c675b 100644 --- a/apps/api/src/app/endpoints/ai/ai.controller.ts +++ b/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 }); diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 18f923112..313721a17 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/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 ) diff --git a/apps/api/src/app/subscription/subscription.service.ts b/apps/api/src/app/subscription/subscription.service.ts index 2e5724c96..d233e475c 100644 --- a/apps/api/src/app/subscription/subscription.service.ts +++ b/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: [ diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 2a48b2583..774f9746b 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/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 diff --git a/apps/api/src/models/rule.ts b/apps/api/src/models/rule.ts index 187527fbb..0870be03e 100644 --- a/apps/api/src/models/rule.ts +++ b/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 implements RuleInterface { 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 implements RuleInterface { PortfolioReportRule['configuration'] >; + public abstract getName(): string; + public abstract getSettings(aUserSettings: UserSettings): T; } diff --git a/apps/api/src/models/rules/account-cluster-risk/current-investment.ts b/apps/api/src/models/rules/account-cluster-risk/current-investment.ts index 564af935d..76b7e2f94 100644 --- a/apps/api/src/models/rules/account-cluster-risk/current-investment.ts +++ b/apps/api/src/models/rules/account-cluster-risk/current-investment.ts @@ -15,8 +15,7 @@ export class AccountClusterRiskCurrentInvestment extends Rule { 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 { }; } + public getName() { + return 'Investment'; + } + public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { return { baseCurrency, diff --git a/apps/api/src/models/rules/account-cluster-risk/single-account.ts b/apps/api/src/models/rules/account-cluster-risk/single-account.ts index ef549e579..50f49efab 100644 --- a/apps/api/src/models/rules/account-cluster-risk/single-account.ts +++ b/apps/api/src/models/rules/account-cluster-risk/single-account.ts @@ -11,8 +11,7 @@ export class AccountClusterRiskSingleAccount extends Rule { 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 { return undefined; } + public getName() { + return 'Single Account'; + } + public getSettings({ xRayRules }: UserSettings): RuleSettings { return { isActive: xRayRules?.[this.getKey()].isActive ?? true diff --git a/apps/api/src/models/rules/asset-class-cluster-risk/equity.ts b/apps/api/src/models/rules/asset-class-cluster-risk/equity.ts index b67e01e61..b386f3e18 100644 --- a/apps/api/src/models/rules/asset-class-cluster-risk/equity.ts +++ b/apps/api/src/models/rules/asset-class-cluster-risk/equity.ts @@ -11,8 +11,7 @@ export class AssetClassClusterRiskEquity extends Rule { holdings: PortfolioPosition[] ) { super(exchangeRateDataService, { - key: AssetClassClusterRiskEquity.name, - name: 'Equity' + key: AssetClassClusterRiskEquity.name }); this.holdings = holdings; @@ -78,6 +77,10 @@ export class AssetClassClusterRiskEquity extends Rule { }; } + public getName() { + return 'Equity'; + } + public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { return { baseCurrency, diff --git a/apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts b/apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts index eb744a143..918856c45 100644 --- a/apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts +++ b/apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts @@ -11,8 +11,7 @@ export class AssetClassClusterRiskFixedIncome extends Rule { 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 { }; } + public getName() { + return 'Fixed Income'; + } + public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { return { baseCurrency, diff --git a/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts b/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts index 90ee29c54..6de686a9e 100644 --- a/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts +++ b/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts @@ -11,8 +11,7 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule { holdings: PortfolioPosition[] ) { super(exchangeRateDataService, { - key: CurrencyClusterRiskCurrentInvestment.name, - name: 'Investment' + key: CurrencyClusterRiskCurrentInvestment.name }); this.holdings = holdings; @@ -73,6 +72,10 @@ export class CurrencyClusterRiskCurrentInvestment extends Rule { }; } + public getName() { + return 'Investment'; + } + public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { return { baseCurrency, diff --git a/apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts b/apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts index 15e113927..8f3ef0d65 100644 --- a/apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts +++ b/apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts @@ -13,8 +13,7 @@ export class EconomicMarketClusterRiskDevelopedMarkets extends Rule { 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 { }; } + public getName() { + return 'Developed Markets'; + } + public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { return { baseCurrency, diff --git a/apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts b/apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts index 8fccdf1d8..48eff22cf 100644 --- a/apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts +++ b/apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts @@ -13,8 +13,7 @@ export class EconomicMarketClusterRiskEmergingMarkets extends Rule { 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 { }; } + public getName() { + return 'Emerging Markets'; + } + public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { return { baseCurrency, diff --git a/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts b/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts index d13f2ffc5..8b53e6799 100644 --- a/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts +++ b/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts @@ -11,8 +11,7 @@ export class EmergencyFundSetup extends Rule { 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 { return undefined; } + public getName() { + return 'Emergency Fund: Set up'; + } + public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { return { baseCurrency, diff --git a/apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts b/apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts index fa9d7e7bc..4298eed44 100644 --- a/apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts +++ b/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 { 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 { 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 { }; } + public getName() { + return this.i18nService.getTranslation({ + id: 'rule.feeRatioInitialInvestment', + languageCode: this.getLanguageCode() + }); + } + public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { return { baseCurrency, diff --git a/apps/api/src/models/rules/regional-market-cluster-risk/asia-pacific.ts b/apps/api/src/models/rules/regional-market-cluster-risk/asia-pacific.ts index d49849d54..823da9e27 100644 --- a/apps/api/src/models/rules/regional-market-cluster-risk/asia-pacific.ts +++ b/apps/api/src/models/rules/regional-market-cluster-risk/asia-pacific.ts @@ -14,8 +14,7 @@ export class RegionalMarketClusterRiskAsiaPacific extends Rule { 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 { }; } + public getName() { + return 'Asia-Pacific'; + } + public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { return { baseCurrency, diff --git a/apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts b/apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts index 2d9a3b394..b1ab74e1f 100644 --- a/apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts +++ b/apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts @@ -14,8 +14,7 @@ export class RegionalMarketClusterRiskEmergingMarkets extends Rule { 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 { }; } + public getName() { + return 'Emerging Markets'; + } + public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { return { baseCurrency, diff --git a/apps/api/src/models/rules/regional-market-cluster-risk/europe.ts b/apps/api/src/models/rules/regional-market-cluster-risk/europe.ts index d33190ceb..3b069cf57 100644 --- a/apps/api/src/models/rules/regional-market-cluster-risk/europe.ts +++ b/apps/api/src/models/rules/regional-market-cluster-risk/europe.ts @@ -14,8 +14,7 @@ export class RegionalMarketClusterRiskEurope extends Rule { europeValueInBaseCurrency: number ) { super(exchangeRateDataService, { - key: RegionalMarketClusterRiskEurope.name, - name: 'Europe' + key: RegionalMarketClusterRiskEurope.name }); this.currentValueInBaseCurrency = currentValueInBaseCurrency; @@ -66,6 +65,10 @@ export class RegionalMarketClusterRiskEurope extends Rule { }; } + public getName() { + return 'Europe'; + } + public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { return { baseCurrency, diff --git a/apps/api/src/models/rules/regional-market-cluster-risk/japan.ts b/apps/api/src/models/rules/regional-market-cluster-risk/japan.ts index 4694b0006..511393cfb 100644 --- a/apps/api/src/models/rules/regional-market-cluster-risk/japan.ts +++ b/apps/api/src/models/rules/regional-market-cluster-risk/japan.ts @@ -14,8 +14,7 @@ export class RegionalMarketClusterRiskJapan extends Rule { japanValueInBaseCurrency: number ) { super(exchangeRateDataService, { - key: RegionalMarketClusterRiskJapan.name, - name: 'Japan' + key: RegionalMarketClusterRiskJapan.name }); this.currentValueInBaseCurrency = currentValueInBaseCurrency; @@ -66,6 +65,10 @@ export class RegionalMarketClusterRiskJapan extends Rule { }; } + public getName() { + return 'Japan'; + } + public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { return { baseCurrency, diff --git a/apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts b/apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts index 4563b7c54..5bea57db1 100644 --- a/apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts +++ b/apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts @@ -14,8 +14,7 @@ export class RegionalMarketClusterRiskNorthAmerica extends Rule { 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 { }; } + public getName() { + return 'North America'; + } + public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { return { baseCurrency, diff --git a/apps/api/src/services/i18n/i18n.service.ts b/apps/api/src/services/i18n/i18n.service.ts index 13193fc6a..e928775d1 100644 --- a/apps/api/src/services/i18n/i18n.service.ts +++ b/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 { 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(); } diff --git a/apps/client/src/app/components/access-table/access-table.component.ts b/apps/client/src/app/components/access-table/access-table.component.ts index e70b6684a..bcd5839e0 100644 --- a/apps/client/src/app/components/access-table/access-table.component.ts +++ b/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}`; } diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.ts b/apps/client/src/app/components/admin-settings/admin-settings.component.ts index 3ff8370cd..68db9eaac 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.ts +++ b/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}`; diff --git a/apps/client/src/app/pages/i18n/i18n-page.html b/apps/client/src/app/pages/i18n/i18n-page.html index b23129c45..e1c244265 100644 --- a/apps/client/src/app/pages/i18n/i18n-page.html +++ b/apps/client/src/app/pages/i18n/i18n-page.html @@ -11,6 +11,15 @@ performance, portfolio, software, stock, trading, wealth, web3
  • My Account
  • +
  • Fee Ratio
  • +
  • + The fees do exceed {thresholdMax}% of your initial investment + ({feeRatio}%) +
  • +
  • + The fees do not exceed {thresholdMax}% of your initial + investment ({feeRatio}%) +
  • Open Source Wealth Management Software