From 363684526f9cfddd3e5e097fcde5591134ff309b Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:17:52 +0200 Subject: [PATCH] Task/localize country names (#6995) * Localize country names * Update changelog --- CHANGELOG.md | 1 + .../asset-profile-dialog.component.ts | 6 ++-- .../asset-profile-dialog.html | 7 ++++- .../holding-detail-dialog.component.ts | 12 ++++++-- .../holding-detail-dialog.html | 7 ++++- .../allocations/allocations-page.component.ts | 11 ++++--- .../app/pages/public/public-page.component.ts | 8 ++--- libs/common/src/lib/helper.ts | 14 +++++++++ libs/ui/src/lib/i18n.ts | 29 ------------------- .../portfolio-proportion-chart.component.ts | 6 ++-- .../world-map-chart.component.ts | 16 ++++++++-- 11 files changed, 68 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e13d948b..5f17f1288 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Harmonized the sector names across the data providers +- Localized the country names - Localized the sector names - Centralized the asset profile override logic for manual adjustments - Improved the styling in the user detail dialog of the admin control panel’s users section diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index fc08c3680..ce997cf27 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -8,6 +8,7 @@ import { UpdateAssetProfileDto } from '@ghostfolio/common/dtos'; import { canDeleteAssetProfile, DATE_FORMAT, + getCountryName, getCurrencyFromSymbol, isCurrency } from '@ghostfolio/common/helper'; @@ -224,6 +225,7 @@ export class GfAssetProfileDialogComponent implements OnInit { value: 'max' } ]; + protected readonly getCountryName = getCountryName; protected historicalDataItems: LineChartItem[]; protected isBenchmark = false; protected isDataGatheringEnabled: boolean; @@ -369,9 +371,9 @@ export class GfAssetProfileDialogComponent implements OnInit { this.assetProfile?.countries && this.assetProfile.countries.length > 0 ) { - for (const { code, name, weight } of this.assetProfile.countries) { + for (const { code, weight } of this.assetProfile.countries) { this.countries[code] = { - name, + name: getCountryName({ code, locale: this.data.locale }), value: weight }; } diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html index 474fff3ca..d9a5354e5 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -269,7 +269,12 @@ i18n size="medium" [locale]="data.locale" - [value]="assetProfile?.countries[0].name" + [value]=" + getCountryName({ + code: assetProfile?.countries[0].code, + locale: data.locale + }) + " >Country diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index e745decd0..68bb1215a 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -6,7 +6,11 @@ import { NUMERICAL_PRECISION_THRESHOLD_6_FIGURES } from '@ghostfolio/common/config'; import { CreateOrderDto } from '@ghostfolio/common/dtos'; -import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper'; +import { + DATE_FORMAT, + downloadAsFile, + getCountryName +} from '@ghostfolio/common/helper'; import { Activity, DataProviderInfo, @@ -121,6 +125,7 @@ export class GfHoldingDetailDialogComponent implements OnInit { public dividendInBaseCurrencyPrecision = 2; public dividendYieldPercentWithCurrencyEffect: number; public feeInBaseCurrency: number; + public getCountryName = getCountryName; public hasPermissionToCreateOwnTag: boolean; public hasPermissionToReadMarketDataOfOwnAssetProfile: boolean; public historicalDataItems: LineChartItem[]; @@ -434,7 +439,10 @@ export class GfHoldingDetailDialogComponent implements OnInit { if (SymbolProfile?.countries?.length > 0) { for (const country of SymbolProfile.countries) { this.countries[country.code] = { - name: country.name, + name: getCountryName({ + code: country.code, + locale: this.data.locale + }), value: country.weight }; } diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html index 478a8e5a3..609eec3c0 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -272,7 +272,12 @@ i18n size="medium" [locale]="data.locale" - [value]="SymbolProfile.countries[0].name" + [value]=" + getCountryName({ + code: SymbolProfile.countries[0].code, + locale: data.locale + }) + " >Country diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts index 792f32bf5..d0eb3788b 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts @@ -3,7 +3,7 @@ import { AccountDetailDialogParams } from '@ghostfolio/client/components/account import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { MAX_TOP_HOLDINGS, UNKNOWN_KEY } from '@ghostfolio/common/config'; -import { prettifySymbol } from '@ghostfolio/common/helper'; +import { getCountryName, prettifySymbol } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier, HoldingWithParents, @@ -353,7 +353,7 @@ export class GfAllocationsPageComponent implements OnInit { if (position.assetProfile.countries.length > 0) { for (const country of position.assetProfile.countries) { - const { code, continent, name, weight } = country; + const { code, continent, weight } = country; if (this.continents[continent]?.value) { this.continents[continent].value += @@ -363,7 +363,7 @@ export class GfAllocationsPageComponent implements OnInit { : position.valueInPercentage); } else { this.continents[continent] = { - name: continent, + name: translate(continent), value: weight * (isNumber(position.valueInBaseCurrency) @@ -380,7 +380,10 @@ export class GfAllocationsPageComponent implements OnInit { : position.valueInPercentage); } else { this.countries[code] = { - name, + name: getCountryName({ + code, + locale: this.user?.settings?.locale + }), value: weight * (isNumber(position.valueInBaseCurrency) diff --git a/apps/client/src/app/pages/public/public-page.component.ts b/apps/client/src/app/pages/public/public-page.component.ts index 52a7864ac..43d961c1d 100644 --- a/apps/client/src/app/pages/public/public-page.component.ts +++ b/apps/client/src/app/pages/public/public-page.component.ts @@ -1,5 +1,5 @@ import { UNKNOWN_KEY } from '@ghostfolio/common/config'; -import { prettifySymbol } from '@ghostfolio/common/helper'; +import { getCountryName, prettifySymbol } from '@ghostfolio/common/helper'; import { InfoItem, PortfolioPosition, @@ -186,14 +186,14 @@ export class GfPublicPageComponent implements OnInit { if (position.assetProfile.countries.length > 0) { for (const country of position.assetProfile.countries) { - const { code, continent, name, weight } = country; + const { code, continent, weight } = country; if (this.continents[continent]?.value) { this.continents[continent].value += weight * (position.valueInBaseCurrency ?? 0); } else { this.continents[continent] = { - name: continent, + name: translate(continent), value: weight * (this.publicPortfolioDetails.holdings[symbol] @@ -206,7 +206,7 @@ export class GfPublicPageComponent implements OnInit { weight * (position.valueInBaseCurrency ?? 0); } else { this.countries[code] = { - name, + name: getCountryName({ code }), value: weight * (this.publicPortfolioDetails.holdings[symbol] diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index 02bd26b90..db320c7cb 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -258,6 +258,20 @@ export function getCurrencyFromSymbol(aSymbol = '') { return aSymbol.replace(DEFAULT_CURRENCY, ''); } +export function getCountryName({ + code, + locale = getLocale() +}: { + code: string; + locale?: string; +}) { + try { + return new Intl.DisplayNames([locale], { type: 'region' }).of(code) ?? code; + } catch { + return code; + } +} + export function getDateFnsLocale(aLanguageCode?: string) { if (aLanguageCode === 'ca') { return ca; diff --git a/libs/ui/src/lib/i18n.ts b/libs/ui/src/lib/i18n.ts index f6f1e8ff9..2c037c7d1 100644 --- a/libs/ui/src/lib/i18n.ts +++ b/libs/ui/src/lib/i18n.ts @@ -75,35 +75,6 @@ const locales = { Oceania: $localize`Oceania`, 'South America': $localize`South America`, - // Countries - Armenia: $localize`Armenia`, - Argentina: $localize`Argentina`, - Australia: $localize`Australia`, - Austria: $localize`Austria`, - Belgium: $localize`Belgium`, - 'British Virgin Islands': $localize`British Virgin Islands`, - Bulgaria: $localize`Bulgaria`, - Canada: $localize`Canada`, - 'Czech Republic': $localize`Czech Republic`, - Finland: $localize`Finland`, - France: $localize`France`, - Germany: $localize`Germany`, - India: $localize`India`, - Indonesia: $localize`Indonesia`, - Italy: $localize`Italy`, - Japan: $localize`Japan`, - Netherlands: $localize`Netherlands`, - 'New Zealand': $localize`New Zealand`, - Poland: $localize`Poland`, - Romania: $localize`Romania`, - Singapore: $localize`Singapore`, - 'South Africa': $localize`South Africa`, - Switzerland: $localize`Switzerland`, - Thailand: $localize`Thailand`, - Ukraine: $localize`Ukraine`, - 'United Kingdom': $localize`United Kingdom`, - 'United States': $localize`United States`, - // Fear and Greed Index EXTREME_FEAR: $localize`Extreme Fear`, EXTREME_GREED: $localize`Extreme Greed`, diff --git a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts index 4021bf97f..7c17b587c 100644 --- a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts +++ b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts @@ -37,8 +37,6 @@ import Color from 'color'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import OpenColor from 'open-color'; -import { translate } from '../i18n'; - const { blue, cyan, @@ -390,7 +388,7 @@ export class GfPortfolioProportionChartComponent return value > 0 ? isUUID(symbol) - ? (translate(this.data[symbol]?.name) ?? symbol) + ? (this.data[symbol]?.name ?? symbol) : symbol : ''; }, @@ -453,7 +451,7 @@ export class GfPortfolioProportionChartComponent symbol = $localize`No data available`; } - const name = translate(this.data[symbol]?.name); + const name = this.data[symbol]?.name; let sum = 0; diff --git a/libs/ui/src/lib/world-map-chart/world-map-chart.component.ts b/libs/ui/src/lib/world-map-chart/world-map-chart.component.ts index f86d4d010..0d5eb7387 100644 --- a/libs/ui/src/lib/world-map-chart/world-map-chart.component.ts +++ b/libs/ui/src/lib/world-map-chart/world-map-chart.component.ts @@ -1,4 +1,8 @@ -import { getLocale, getNumberFormatGroup } from '@ghostfolio/common/helper'; +import { + getCountryName, + getLocale, + getNumberFormatGroup +} from '@ghostfolio/common/helper'; import { ChangeDetectionStrategy, @@ -25,7 +29,7 @@ export class GfWorldMapChartComponent implements OnChanges, OnDestroy { @Input() locale = getLocale(); public isLoading = true; - public svgMapElement; + public svgMapElement: any; public constructor(private changeDetectorRef: ChangeDetectorRef) {} @@ -88,6 +92,14 @@ export class GfWorldMapChartComponent implements OnChanges, OnDestroy { targetElementID: 'svgMap' }); + this.svgMapElement.options.countryNames = Object.keys( + this.svgMapElement.countries + ).reduce<{ [code: string]: string }>((names, code) => { + names[code] = getCountryName({ code, locale: this.locale }); + + return names; + }, {}); + setTimeout(() => { this.isLoading = false;