Browse Source

Task/localize default account name created during user registration (#7888)

* Localize default account name created during user registration

* Update changelog
pull/7908/head
Thomas Kaul 1 week ago
committed by GitHub
parent
commit
1e3b88add8
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 8
      apps/api/src/app/user/user.controller.ts
  3. 8
      apps/api/src/app/user/user.service.ts
  4. 40
      apps/api/src/helper/language.helper.spec.ts
  5. 24
      apps/api/src/helper/language.helper.ts
  6. 17
      apps/api/src/middlewares/language-redirect.middleware.ts
  7. 2
      apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts
  8. 7
      libs/common/src/lib/dtos/create-user.dto.ts
  9. 2
      libs/common/src/lib/dtos/index.ts
  10. 5
      libs/ui/src/lib/services/data.service.ts

2
CHANGELOG.md

@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Localized the default account name created during user registration
- Extended the `POST api/v1/user` endpoint by the language code
- Improved the language localization for German (`de`)
- Upgraded `@internationalized/number` from version `3.6.7` to `3.6.8`
- Upgraded `bull-board` from version `9.8.0` to `9.9.0`

8
apps/api/src/app/user/user.controller.ts

@ -15,6 +15,7 @@ import {
THROTTLE_SIGNUP_TTL
} from '@ghostfolio/common/config';
import {
CreateUserDto,
DeleteOwnUserDto,
UpdateOwnAccessTokenDto,
UpdateUserSettingDto
@ -147,7 +148,7 @@ export class UserController {
}
})
@UseGuards(CustomThrottlerGuard)
public async signupUser(): Promise<UserItem> {
public async signupUser(@Body() data: CreateUserDto): Promise<UserItem> {
const isUserSignupEnabled =
await this.propertyService.isUserSignupEnabled();
@ -158,7 +159,10 @@ export class UserController {
);
}
const { accessToken, id, role } = await this.userService.createUser();
const { accessToken, id, role } = await this.userService.createUser({
data: {},
languageCode: data.languageCode
});
return {
accessToken,

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

@ -2,6 +2,7 @@ import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.ser
import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service';
import { environment } from '@ghostfolio/api/environments/environment';
import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event';
import { getSupportedLanguageCode } from '@ghostfolio/api/helper/language.helper';
import { getRandomString } from '@ghostfolio/api/helper/string.helper';
import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment';
import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account';
@ -28,7 +29,6 @@ import { TagService } from '@ghostfolio/api/services/tag/tag.service';
import {
DEFAULT_CURRENCY,
DEFAULT_DATE_RANGE,
DEFAULT_LANGUAGE_CODE,
DEFAULT_LOCALE,
PROPERTY_API_KEY_GHOSTFOLIO,
PROPERTY_IS_READ_ONLY_MODE,
@ -692,9 +692,11 @@ export class UserService {
public async createUser(
{
data
data,
languageCode
}: {
data: Prisma.UserCreateInput;
languageCode?: string;
} = { data: {} }
): Promise<User> {
if (!data.provider) {
@ -715,7 +717,7 @@ export class UserService {
currency: DEFAULT_CURRENCY,
name: this.i18nService.getTranslation({
id: 'myAccount',
languageCode: DEFAULT_LANGUAGE_CODE // TODO
languageCode: getSupportedLanguageCode(languageCode)
})
}
},

40
apps/api/src/helper/language.helper.spec.ts

@ -0,0 +1,40 @@
import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config';
import {
getLanguageCodeFromHeader,
getSupportedLanguageCode
} from './language.helper';
describe('getLanguageCodeFromHeader', () => {
it('should get the primary language code', () => {
expect(getLanguageCodeFromHeader('de-CH,de;q=0.9,en;q=0.8')).toEqual('de');
});
it('should use the default language code if the header is missing', () => {
expect(getLanguageCodeFromHeader()).toEqual(DEFAULT_LANGUAGE_CODE);
});
it('should use the default language code if the language is not supported', () => {
expect(getLanguageCodeFromHeader('xx')).toEqual(DEFAULT_LANGUAGE_CODE);
});
});
describe('getSupportedLanguageCode', () => {
it('should return a supported language code', () => {
expect(getSupportedLanguageCode('de')).toEqual('de');
});
it('should ignore the region subtag', () => {
expect(getSupportedLanguageCode('de-CH')).toEqual('de');
});
it('should ignore the case', () => {
expect(getSupportedLanguageCode('DE')).toEqual('de');
});
it('should use the default language code if the language is not supported', () => {
expect(getSupportedLanguageCode()).toEqual(DEFAULT_LANGUAGE_CODE);
expect(getSupportedLanguageCode('')).toEqual(DEFAULT_LANGUAGE_CODE);
expect(getSupportedLanguageCode('xx')).toEqual(DEFAULT_LANGUAGE_CODE);
});
});

24
apps/api/src/helper/language.helper.ts

@ -0,0 +1,24 @@
import {
DEFAULT_LANGUAGE_CODE,
SUPPORTED_LANGUAGE_CODES
} from '@ghostfolio/common/config';
type SupportedLanguageCode = (typeof SUPPORTED_LANGUAGE_CODES)[number];
export function getLanguageCodeFromHeader(
acceptLanguage?: string
): SupportedLanguageCode {
return getSupportedLanguageCode(acceptLanguage?.split(',')[0]);
}
export function getSupportedLanguageCode(
languageCode?: string
): SupportedLanguageCode {
const languageCodeToUse = languageCode
?.split('-')[0]
.toLowerCase() as SupportedLanguageCode;
return SUPPORTED_LANGUAGE_CODES.includes(languageCodeToUse)
? languageCodeToUse
: DEFAULT_LANGUAGE_CODE;
}

17
apps/api/src/middlewares/language-redirect.middleware.ts

@ -1,8 +1,5 @@
import { environment } from '@ghostfolio/api/environments/environment';
import {
DEFAULT_LANGUAGE_CODE,
SUPPORTED_LANGUAGE_CODES
} from '@ghostfolio/common/config';
import { getLanguageCodeFromHeader } from '@ghostfolio/api/helper/language.helper';
import { NextFunction, Request, Response } from 'express';
import { StatusCodes } from 'http-status-codes';
@ -20,15 +17,9 @@ export function languageRedirectMiddleware(
return next();
}
let languageCode = DEFAULT_LANGUAGE_CODE;
try {
const code = request.headers['accept-language'].split(',')[0].split('-')[0];
if ((SUPPORTED_LANGUAGE_CODES as readonly string[]).includes(code)) {
languageCode = code;
}
} catch {}
const languageCode = getLanguageCodeFromHeader(
request.headers['accept-language']
);
return response.redirect(
StatusCodes.MOVED_PERMANENTLY,

2
apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts

@ -76,7 +76,7 @@ export class GfUserAccountRegistrationDialogComponent {
protected createAccount() {
this.dataService
.postUser()
.postUser({ languageCode: document.documentElement.lang })
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ accessToken, authToken, role }) => {
this.accessToken = accessToken;

7
libs/common/src/lib/dtos/create-user.dto.ts

@ -0,0 +1,7 @@
import { IsOptional, IsString } from 'class-validator';
export class CreateUserDto {
@IsOptional()
@IsString()
languageCode?: string;
}

2
libs/common/src/lib/dtos/index.ts

@ -10,6 +10,7 @@ import { CreateAssetProfileDto } from './create-asset-profile.dto';
import { CreateOrderDto } from './create-order.dto';
import { CreatePlatformDto } from './create-platform.dto';
import { CreateTagDto } from './create-tag.dto';
import { CreateUserDto } from './create-user.dto';
import { CreateWatchlistItemDto } from './create-watchlist-item.dto';
import { DeleteOwnUserDto } from './delete-own-user.dto';
import { HoldingDto } from './holding.dto';
@ -43,6 +44,7 @@ export {
CreateOrderDto,
CreatePlatformDto,
CreateTagDto,
CreateUserDto,
CreateWatchlistItemDto,
DeleteOwnUserDto,
HoldingDto,

5
libs/ui/src/lib/services/data.service.ts

@ -4,6 +4,7 @@ import {
CreateAccountDto,
CreateOrderDto,
CreateTagDto,
CreateUserDto,
CreateWatchlistItemDto,
DeleteOwnUserDto,
TransferBalanceDto,
@ -896,8 +897,8 @@ export class DataService {
return this.http.post<Tag>(`/api/v1/tags`, aTag);
}
public postUser() {
return this.http.post<UserItem>('/api/v1/user', {});
public postUser(aData: CreateUserDto) {
return this.http.post<UserItem>('/api/v1/user', aData);
}
public postWatchlistItem(watchlistItem: CreateWatchlistItemDto) {

Loading…
Cancel
Save