From 73bc4851844beab1ab4fae46117c95faeaf5c00a Mon Sep 17 00:00:00 2001 From: rohit Date: Thu, 10 Jul 2025 03:44:38 +0530 Subject: [PATCH] Feature/Set up language localization for Regional Market Cluster Risks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added i18n support setup for static X-ray rule: Regional Market Cluster Risks - Followed the approach in #4959 as guidance Note: I haven’t made changes to the `.xlf` files yet. I understand they can be generated using `i18n-page.html`, but when I tried, the generated files didn’t include the expected translation entries for this rule. Should I manually update all `.xlf` files to add the translations? --- .../src/app/portfolio/portfolio.service.ts | 10 +++ apps/api/src/app/user/user.service.ts | 10 +++ .../asia-pacific.ts | 44 +++++++++--- .../emerging-markets.ts | 44 +++++++++--- .../regional-market-cluster-risk/europe.ts | 44 +++++++++--- .../regional-market-cluster-risk/japan.ts | 44 +++++++++--- .../north-america.ts | 44 +++++++++--- apps/client/src/app/pages/i18n/i18n-page.html | 71 +++++++++++++++++++ 8 files changed, 256 insertions(+), 55 deletions(-) diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 4d09edf8b..88d6e7d60 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -1272,26 +1272,36 @@ export class PortfolioService { [ new RegionalMarketClusterRiskAsiaPacific( this.exchangeRateDataService, + this.i18nService, + userSettings.language, marketsAdvancedTotalInBaseCurrency, marketsAdvanced.asiaPacific.valueInBaseCurrency ), new RegionalMarketClusterRiskEmergingMarkets( this.exchangeRateDataService, + this.i18nService, + userSettings.language, marketsAdvancedTotalInBaseCurrency, marketsAdvanced.emergingMarkets.valueInBaseCurrency ), new RegionalMarketClusterRiskEurope( this.exchangeRateDataService, + this.i18nService, + userSettings.language, marketsAdvancedTotalInBaseCurrency, marketsAdvanced.europe.valueInBaseCurrency ), new RegionalMarketClusterRiskJapan( this.exchangeRateDataService, + this.i18nService, + userSettings.language, marketsAdvancedTotalInBaseCurrency, marketsAdvanced.japan.valueInBaseCurrency ), new RegionalMarketClusterRiskNorthAmerica( this.exchangeRateDataService, + this.i18nService, + userSettings.language, marketsAdvancedTotalInBaseCurrency, marketsAdvanced.northAmerica.valueInBaseCurrency ) diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index a69167b3b..a043761fa 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -329,28 +329,38 @@ export class UserService { ).getSettings(user.settings.settings), RegionalMarketClusterRiskAsiaPacific: new RegionalMarketClusterRiskAsiaPacific( + undefined, + undefined, undefined, undefined, undefined ).getSettings(user.settings.settings), RegionalMarketClusterRiskEmergingMarkets: new RegionalMarketClusterRiskEmergingMarkets( + undefined, + undefined, undefined, undefined, undefined ).getSettings(user.settings.settings), RegionalMarketClusterRiskEurope: new RegionalMarketClusterRiskEurope( + undefined, + undefined, undefined, undefined, undefined ).getSettings(user.settings.settings), RegionalMarketClusterRiskJapan: new RegionalMarketClusterRiskJapan( + undefined, + undefined, undefined, undefined, undefined ).getSettings(user.settings.settings), RegionalMarketClusterRiskNorthAmerica: new RegionalMarketClusterRiskNorthAmerica( + undefined, + undefined, undefined, undefined, undefined 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 cadb2f9cd..813e9479b 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 @@ -1,5 +1,6 @@ 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'; import { Settings } from './interfaces/rule-settings.interface'; @@ -10,10 +11,13 @@ export class RegionalMarketClusterRiskAsiaPacific extends Rule { public constructor( protected exchangeRateDataService: ExchangeRateDataService, + private i18nService: I18nService, + languageCode: string, currentValueInBaseCurrency: number, asiaPacificValueInBaseCurrency: number ) { super(exchangeRateDataService, { + languageCode, key: RegionalMarketClusterRiskAsiaPacific.name }); @@ -28,26 +32,40 @@ export class RegionalMarketClusterRiskAsiaPacific extends Rule { if (asiaPacificMarketValueRatio > ruleSettings.thresholdMax) { return { - evaluation: `The Asia-Pacific market contribution of your current investment (${(asiaPacificMarketValueRatio * 100).toPrecision(3)}%) exceeds ${( - ruleSettings.thresholdMax * 100 - ).toPrecision(3)}%`, + evaluation: this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskAsiaPacific.false.max', + languageCode: this.getLanguageCode(), + placeholders: { + valueRatio: (asiaPacificMarketValueRatio * 100).toPrecision(3), + thresholdMax: (ruleSettings.thresholdMax * 100).toPrecision(3) + } + }), value: false }; } else if (asiaPacificMarketValueRatio < ruleSettings.thresholdMin) { return { - evaluation: `The Asia-Pacific market contribution of your current investment (${(asiaPacificMarketValueRatio * 100).toPrecision(3)}%) is below ${( - ruleSettings.thresholdMin * 100 - ).toPrecision(3)}%`, + evaluation: this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskAsiaPacific.false.min', + languageCode: this.getLanguageCode(), + placeholders: { + valueRatio: (asiaPacificMarketValueRatio * 100).toPrecision(3), + thresholdMin: (ruleSettings.thresholdMin * 100).toPrecision(3) + } + }), value: false }; } return { - evaluation: `The Asia-Pacific market contribution of your current investment (${(asiaPacificMarketValueRatio * 100).toPrecision(3)}%) is within the range of ${( - ruleSettings.thresholdMin * 100 - ).toPrecision( - 3 - )}% and ${(ruleSettings.thresholdMax * 100).toPrecision(3)}%`, + evaluation: this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskAsiaPacific.true', + languageCode: this.getLanguageCode(), + placeholders: { + valueRatio: (asiaPacificMarketValueRatio * 100).toPrecision(3), + thresholdMin: (ruleSettings.thresholdMin * 100).toPrecision(3), + thresholdMax: (ruleSettings.thresholdMax * 100).toPrecision(3) + } + }), value: true }; } @@ -70,6 +88,10 @@ export class RegionalMarketClusterRiskAsiaPacific extends Rule { } public getName() { + return this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskAsiaPacific', + languageCode: this.getLanguageCode() + }); return 'Asia-Pacific'; } 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 5c582834c..f25bee18a 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 @@ -1,5 +1,6 @@ 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'; import { Settings } from './interfaces/rule-settings.interface'; @@ -10,10 +11,13 @@ export class RegionalMarketClusterRiskEmergingMarkets extends Rule { public constructor( protected exchangeRateDataService: ExchangeRateDataService, + private i18nService: I18nService, + languageCode: string, currentValueInBaseCurrency: number, emergingMarketsValueInBaseCurrency: number ) { super(exchangeRateDataService, { + languageCode, key: RegionalMarketClusterRiskEmergingMarkets.name }); @@ -30,26 +34,40 @@ export class RegionalMarketClusterRiskEmergingMarkets extends Rule { if (emergingMarketsValueRatio > ruleSettings.thresholdMax) { return { - evaluation: `The Emerging Markets contribution of your current investment (${(emergingMarketsValueRatio * 100).toPrecision(3)}%) exceeds ${( - ruleSettings.thresholdMax * 100 - ).toPrecision(3)}%`, + evaluation: this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskEmergingMarkets.false.max', + languageCode: this.getLanguageCode(), + placeholders: { + valueRatio: (emergingMarketsValueRatio * 100).toPrecision(3), + thresholdMax: (ruleSettings.thresholdMax * 100).toPrecision(3) + } + }), value: false }; } else if (emergingMarketsValueRatio < ruleSettings.thresholdMin) { return { - evaluation: `The Emerging Markets contribution of your current investment (${(emergingMarketsValueRatio * 100).toPrecision(3)}%) is below ${( - ruleSettings.thresholdMin * 100 - ).toPrecision(3)}%`, + evaluation: this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskEmergingMarkets.false.min', + languageCode: this.getLanguageCode(), + placeholders: { + valueRatio: (emergingMarketsValueRatio * 100).toPrecision(3), + thresholdMin: (ruleSettings.thresholdMin * 100).toPrecision(3) + } + }), value: false }; } return { - evaluation: `The Emerging Markets contribution of your current investment (${(emergingMarketsValueRatio * 100).toPrecision(3)}%) is within the range of ${( - ruleSettings.thresholdMin * 100 - ).toPrecision( - 3 - )}% and ${(ruleSettings.thresholdMax * 100).toPrecision(3)}%`, + evaluation: this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskEmergingMarkets.true', + languageCode: this.getLanguageCode(), + placeholders: { + valueRatio: (emergingMarketsValueRatio * 100).toPrecision(3), + thresholdMin: (ruleSettings.thresholdMin * 100).toPrecision(3), + thresholdMax: (ruleSettings.thresholdMax * 100).toPrecision(3) + } + }), value: true }; } @@ -72,6 +90,10 @@ export class RegionalMarketClusterRiskEmergingMarkets extends Rule { } public getName() { + return this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskEmergingMarkets', + languageCode: this.getLanguageCode() + }); return 'Emerging Markets'; } 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 da1781786..f4e1cc6f6 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 @@ -1,5 +1,6 @@ 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'; import { Settings } from './interfaces/rule-settings.interface'; @@ -10,10 +11,13 @@ export class RegionalMarketClusterRiskEurope extends Rule { public constructor( protected exchangeRateDataService: ExchangeRateDataService, + private i18nService: I18nService, + languageCode: string, currentValueInBaseCurrency: number, europeValueInBaseCurrency: number ) { super(exchangeRateDataService, { + languageCode, key: RegionalMarketClusterRiskEurope.name }); @@ -28,26 +32,40 @@ export class RegionalMarketClusterRiskEurope extends Rule { if (europeMarketValueRatio > ruleSettings.thresholdMax) { return { - evaluation: `The Europe market contribution of your current investment (${(europeMarketValueRatio * 100).toPrecision(3)}%) exceeds ${( - ruleSettings.thresholdMax * 100 - ).toPrecision(3)}%`, + evaluation: this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskEurope.false.max', + languageCode: this.getLanguageCode(), + placeholders: { + valueRatio: (europeMarketValueRatio * 100).toPrecision(3), + thresholdMax: (ruleSettings.thresholdMax * 100).toPrecision(3) + } + }), value: false }; } else if (europeMarketValueRatio < ruleSettings.thresholdMin) { return { - evaluation: `The Europe market contribution of your current investment (${(europeMarketValueRatio * 100).toPrecision(3)}%) is below ${( - ruleSettings.thresholdMin * 100 - ).toPrecision(3)}%`, + evaluation: this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskEurope.false.min', + languageCode: this.getLanguageCode(), + placeholders: { + valueRatio: (europeMarketValueRatio * 100).toPrecision(3), + thresholdMin: (ruleSettings.thresholdMin * 100).toPrecision(3) + } + }), value: false }; } return { - evaluation: `The Europe market contribution of your current investment (${(europeMarketValueRatio * 100).toPrecision(3)}%) is within the range of ${( - ruleSettings.thresholdMin * 100 - ).toPrecision( - 3 - )}% and ${(ruleSettings.thresholdMax * 100).toPrecision(3)}%`, + evaluation: this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskEurope.true', + languageCode: this.getLanguageCode(), + placeholders: { + valueRatio: (europeMarketValueRatio * 100).toPrecision(3), + thresholdMin: (ruleSettings.thresholdMin * 100).toPrecision(3), + thresholdMax: (ruleSettings.thresholdMax * 100).toPrecision(3) + } + }), value: true }; } @@ -70,6 +88,10 @@ export class RegionalMarketClusterRiskEurope extends Rule { } public getName() { + return this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskEurope', + languageCode: this.getLanguageCode() + }); return 'Europe'; } 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 915f3995b..f6d3d1145 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 @@ -1,5 +1,6 @@ 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'; import { Settings } from './interfaces/rule-settings.interface'; @@ -10,10 +11,13 @@ export class RegionalMarketClusterRiskJapan extends Rule { public constructor( protected exchangeRateDataService: ExchangeRateDataService, + private i18nService: I18nService, + languageCode: string, currentValueInBaseCurrency: number, japanValueInBaseCurrency: number ) { super(exchangeRateDataService, { + languageCode, key: RegionalMarketClusterRiskJapan.name }); @@ -28,26 +32,40 @@ export class RegionalMarketClusterRiskJapan extends Rule { if (japanMarketValueRatio > ruleSettings.thresholdMax) { return { - evaluation: `The Japan market contribution of your current investment (${(japanMarketValueRatio * 100).toPrecision(3)}%) exceeds ${( - ruleSettings.thresholdMax * 100 - ).toPrecision(3)}%`, + evaluation: this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskJapan.false.max', + languageCode: this.getLanguageCode(), + placeholders: { + valueRatio: (japanMarketValueRatio * 100).toPrecision(3), + thresholdMax: (ruleSettings.thresholdMax * 100).toPrecision(3) + } + }), value: false }; } else if (japanMarketValueRatio < ruleSettings.thresholdMin) { return { - evaluation: `The Japan market contribution of your current investment (${(japanMarketValueRatio * 100).toPrecision(3)}%) is below ${( - ruleSettings.thresholdMin * 100 - ).toPrecision(3)}%`, + evaluation: this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskJapan.false.min', + languageCode: this.getLanguageCode(), + placeholders: { + valueRatio: (japanMarketValueRatio * 100).toPrecision(3), + thresholdMin: (ruleSettings.thresholdMin * 100).toPrecision(3) + } + }), value: false }; } return { - evaluation: `The Japan market contribution of your current investment (${(japanMarketValueRatio * 100).toPrecision(3)}%) is within the range of ${( - ruleSettings.thresholdMin * 100 - ).toPrecision( - 3 - )}% and ${(ruleSettings.thresholdMax * 100).toPrecision(3)}%`, + evaluation: this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskJapan.true', + languageCode: this.getLanguageCode(), + placeholders: { + valueRatio: (japanMarketValueRatio * 100).toPrecision(3), + thresholdMin: (ruleSettings.thresholdMin * 100).toPrecision(3), + thresholdMax: (ruleSettings.thresholdMax * 100).toPrecision(3) + } + }), value: true }; } @@ -70,6 +88,10 @@ export class RegionalMarketClusterRiskJapan extends Rule { } public getName() { + return this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskJapan', + languageCode: this.getLanguageCode() + }); return 'Japan'; } 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 6322708d5..cc52179b1 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 @@ -1,5 +1,6 @@ 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'; import { Settings } from './interfaces/rule-settings.interface'; @@ -10,10 +11,13 @@ export class RegionalMarketClusterRiskNorthAmerica extends Rule { public constructor( protected exchangeRateDataService: ExchangeRateDataService, + private i18nService: I18nService, + languageCode: string, currentValueInBaseCurrency: number, northAmericaValueInBaseCurrency: number ) { super(exchangeRateDataService, { + languageCode, key: RegionalMarketClusterRiskNorthAmerica.name }); @@ -28,26 +32,40 @@ export class RegionalMarketClusterRiskNorthAmerica extends Rule { if (northAmericaMarketValueRatio > ruleSettings.thresholdMax) { return { - evaluation: `The North America market contribution of your current investment (${(northAmericaMarketValueRatio * 100).toPrecision(3)}%) exceeds ${( - ruleSettings.thresholdMax * 100 - ).toPrecision(3)}%`, + evaluation: this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskNorthAmerica.false.max', + languageCode: this.getLanguageCode(), + placeholders: { + valueRatio: (northAmericaMarketValueRatio * 100).toPrecision(3), + thresholdMax: (ruleSettings.thresholdMax * 100).toPrecision(3) + } + }), value: false }; } else if (northAmericaMarketValueRatio < ruleSettings.thresholdMin) { return { - evaluation: `The North America market contribution of your current investment (${(northAmericaMarketValueRatio * 100).toPrecision(3)}%) is below ${( - ruleSettings.thresholdMin * 100 - ).toPrecision(3)}%`, + evaluation: this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskNorthAmerica.false.min', + languageCode: this.getLanguageCode(), + placeholders: { + valueRatio: (northAmericaMarketValueRatio * 100).toPrecision(3), + thresholdMin: (ruleSettings.thresholdMin * 100).toPrecision(3) + } + }), value: false }; } return { - evaluation: `The North America market contribution of your current investment (${(northAmericaMarketValueRatio * 100).toPrecision(3)}%) is within the range of ${( - ruleSettings.thresholdMin * 100 - ).toPrecision( - 3 - )}% and ${(ruleSettings.thresholdMax * 100).toPrecision(3)}%`, + evaluation: this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskNorthAmerica.true', + languageCode: this.getLanguageCode(), + placeholders: { + valueRatio: (northAmericaMarketValueRatio * 100).toPrecision(3), + thresholdMin: (ruleSettings.thresholdMin * 100).toPrecision(3), + thresholdMax: (ruleSettings.thresholdMax * 100).toPrecision(3) + } + }), value: true }; } @@ -70,6 +88,10 @@ export class RegionalMarketClusterRiskNorthAmerica extends Rule { } public getName() { + return this.i18nService.getTranslation({ + id: 'rule.regionClusterRiskNorthAmerica', + languageCode: this.getLanguageCode() + }); return 'North America'; } diff --git a/apps/client/src/app/pages/i18n/i18n-page.html b/apps/client/src/app/pages/i18n/i18n-page.html index 26f0b8dbb..0d521d836 100644 --- a/apps/client/src/app/pages/i18n/i18n-page.html +++ b/apps/client/src/app/pages/i18n/i18n-page.html @@ -12,6 +12,77 @@
  • My Account
  • Account Cluster Risks
  • +
  • Asia-Pacific
  • +
  • + The Asia-Pacific market contribution of your current investment + (${valueRatio}%) is exceeds ${thresholdMax}% +
  • +
  • + The Asia-Pacific market contribution of your current investment + (${valueRatio}%) is below ${thresholdMin}% +
  • +
  • + The Asia-Pacific market contribution of your current investment + (${valueRatio}%) is within the range of + ${thresholdMin}% and ${thresholdMax}% +
  • +
  • Emerging Markets
  • +
  • + The Emerging Markets contribution of your current investment + (${valueRatio}%) exceeds ${thresholdMax}% +
  • +
  • + The Emerging Markets contribution of your current investment + (${valueRatio}%) is below ${thresholdMin}% +
  • +
  • + The Emerging Markets contribution of your current investment + (${valueRatio}%) is within the range of + ${thresholdMin}% and ${thresholdMax}% +
  • + +
  • Europe
  • +
  • + The Europe market contribution of your current investment + (${valueRatio}%) exceeds ${thresholdMax}% +
  • +
  • + The Europe market contribution of your current investment + (${valueRatio}%) is below ${thresholdMin}% +
  • +
  • + The Europe market contribution of your current investment + (${valueRatio}%) is within the range of + ${thresholdMin}% and ${thresholdMax}% +
  • +
  • Japan
  • +
  • + The Japan market contribution of your current investment + (${valueRatio}%) exceeds ${thresholdMax}% +
  • +
  • + The Japan market contribution of your current investment + (${valueRatio}%) is below ${thresholdMin}% +
  • +
  • + The Japan market contribution of your current investment + (${valueRatio}%) is within the range of + ${thresholdMin}% and ${thresholdMax}% +
  • +
  • North America
  • +
  • + The North America market contribution of your current investment + (${valueRatio}%) exceeds ${thresholdMax}% +
  • +
  • + The North America market contribution of your current investment + (${valueRatio}%) is below ${thresholdMin}% +
  • +
  • + The North America market contribution of your current investment + (${valueRatio}%) is within the range of + ${thresholdMin}% and ${thresholdMax}% +
  • Investment
  • Over ${thresholdMax}% of your current investment is at