Translate standard page layouts (#19890)
## Context Standard page layout tabs, page layout widgets, and view field group titles were hardcoded English in the backend. This PR brings them under the same translation pipeline as views. Notes: Once a standard widget/tab/section title is overriden, the backend returns its value without translation
This commit is contained in:
+11
-10
@@ -1,17 +1,18 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
// This file exists solely for Lingui string extraction.
|
||||
// The strings defined here correspond to standard page layout tab titles
|
||||
// so they appear in the .po catalogs and can be translated at resolve time
|
||||
// via generateMessageId hash lookups.
|
||||
export const getStandardPageLayoutTabTitles = () => [
|
||||
t`Home`,
|
||||
t`Timeline`,
|
||||
t`Tasks`,
|
||||
t`Notes`,
|
||||
t`Files`,
|
||||
t`Emails`,
|
||||
t`Calendar`,
|
||||
t`Note`,
|
||||
t`Flow`,
|
||||
msg`Home`,
|
||||
msg`Timeline`,
|
||||
msg`Tasks`,
|
||||
msg`Notes`,
|
||||
msg`Files`,
|
||||
msg`Emails`,
|
||||
msg`Calendar`,
|
||||
msg`Note`,
|
||||
msg`Flow`,
|
||||
msg`Tab 1`,
|
||||
];
|
||||
|
||||
+10
@@ -16,6 +16,7 @@ import {
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
|
||||
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import { type I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type';
|
||||
@@ -41,18 +42,27 @@ export class PageLayoutTabResolver {
|
||||
constructor(
|
||||
private readonly pageLayoutTabService: PageLayoutTabService,
|
||||
private readonly i18nService: I18nService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
) {}
|
||||
|
||||
@ResolveField(() => String)
|
||||
async title(
|
||||
@Parent() tab: PageLayoutTabDTO,
|
||||
@Context() context: I18nContext,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<string> {
|
||||
const i18n = this.i18nService.getI18nInstance(context.req.locale);
|
||||
|
||||
const { twentyStandardFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspace },
|
||||
);
|
||||
|
||||
return resolvePageLayoutTabTitle({
|
||||
title: tab.title,
|
||||
applicationId: tab.applicationId,
|
||||
twentyStandardApplicationId: twentyStandardFlatApplication.id,
|
||||
overrides: tab.overrides,
|
||||
i18nInstance: i18n,
|
||||
});
|
||||
}
|
||||
|
||||
+45
-5
@@ -2,7 +2,6 @@ import { type I18n } from '@lingui/core';
|
||||
|
||||
import { generateMessageId } from 'src/engine/core-modules/i18n/utils/generateMessageId';
|
||||
import { resolvePageLayoutTabTitle } from 'src/engine/metadata-modules/page-layout-tab/utils/resolve-page-layout-tab-title.util';
|
||||
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
|
||||
|
||||
jest.mock('src/engine/core-modules/i18n/utils/generateMessageId');
|
||||
|
||||
@@ -10,6 +9,8 @@ const mockGenerateMessageId = generateMessageId as jest.MockedFunction<
|
||||
typeof generateMessageId
|
||||
>;
|
||||
|
||||
const STANDARD_APPLICATION_ID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
|
||||
|
||||
describe('resolvePageLayoutTabTitle', () => {
|
||||
let mockI18n: jest.Mocked<I18n>;
|
||||
|
||||
@@ -26,7 +27,8 @@ describe('resolvePageLayoutTabTitle', () => {
|
||||
|
||||
const result = resolvePageLayoutTabTitle({
|
||||
title: 'Home',
|
||||
applicationId: TWENTY_STANDARD_APPLICATION.universalIdentifier,
|
||||
applicationId: STANDARD_APPLICATION_ID,
|
||||
twentyStandardApplicationId: STANDARD_APPLICATION_ID,
|
||||
i18nInstance: mockI18n,
|
||||
});
|
||||
|
||||
@@ -41,7 +43,8 @@ describe('resolvePageLayoutTabTitle', () => {
|
||||
|
||||
const result = resolvePageLayoutTabTitle({
|
||||
title: 'My Custom Tab',
|
||||
applicationId: TWENTY_STANDARD_APPLICATION.universalIdentifier,
|
||||
applicationId: STANDARD_APPLICATION_ID,
|
||||
twentyStandardApplicationId: STANDARD_APPLICATION_ID,
|
||||
i18nInstance: mockI18n,
|
||||
});
|
||||
|
||||
@@ -56,7 +59,8 @@ describe('resolvePageLayoutTabTitle', () => {
|
||||
|
||||
const result = resolvePageLayoutTabTitle({
|
||||
title: '',
|
||||
applicationId: TWENTY_STANDARD_APPLICATION.universalIdentifier,
|
||||
applicationId: STANDARD_APPLICATION_ID,
|
||||
twentyStandardApplicationId: STANDARD_APPLICATION_ID,
|
||||
i18nInstance: mockI18n,
|
||||
});
|
||||
|
||||
@@ -83,7 +87,8 @@ describe('resolvePageLayoutTabTitle', () => {
|
||||
|
||||
const result = resolvePageLayoutTabTitle({
|
||||
title: source,
|
||||
applicationId: TWENTY_STANDARD_APPLICATION.universalIdentifier,
|
||||
applicationId: STANDARD_APPLICATION_ID,
|
||||
twentyStandardApplicationId: STANDARD_APPLICATION_ID,
|
||||
i18nInstance: mockI18n,
|
||||
});
|
||||
|
||||
@@ -100,6 +105,7 @@ describe('resolvePageLayoutTabTitle', () => {
|
||||
const result = resolvePageLayoutTabTitle({
|
||||
title: 'Home',
|
||||
applicationId: customAppId,
|
||||
twentyStandardApplicationId: STANDARD_APPLICATION_ID,
|
||||
i18nInstance: mockI18n,
|
||||
});
|
||||
|
||||
@@ -107,4 +113,38 @@ describe('resolvePageLayoutTabTitle', () => {
|
||||
expect(mockI18n._).not.toHaveBeenCalled();
|
||||
expect(result).toBe('Home');
|
||||
});
|
||||
|
||||
it('should not translate title when overrides.title is defined', () => {
|
||||
mockGenerateMessageId.mockReturnValue('abc123');
|
||||
mockI18n._.mockReturnValue('Accueil');
|
||||
|
||||
const result = resolvePageLayoutTabTitle({
|
||||
title: 'Home',
|
||||
applicationId: STANDARD_APPLICATION_ID,
|
||||
twentyStandardApplicationId: STANDARD_APPLICATION_ID,
|
||||
overrides: { title: 'Home' },
|
||||
i18nInstance: mockI18n,
|
||||
});
|
||||
|
||||
expect(mockGenerateMessageId).not.toHaveBeenCalled();
|
||||
expect(mockI18n._).not.toHaveBeenCalled();
|
||||
expect(result).toBe('Home');
|
||||
});
|
||||
|
||||
it('should translate title when overrides is defined but overrides.title is not', () => {
|
||||
mockGenerateMessageId.mockReturnValue('abc123');
|
||||
mockI18n._.mockReturnValue('Accueil');
|
||||
|
||||
const result = resolvePageLayoutTabTitle({
|
||||
title: 'Home',
|
||||
applicationId: STANDARD_APPLICATION_ID,
|
||||
twentyStandardApplicationId: STANDARD_APPLICATION_ID,
|
||||
overrides: { icon: 'IconCustom' },
|
||||
i18nInstance: mockI18n,
|
||||
});
|
||||
|
||||
expect(mockGenerateMessageId).toHaveBeenCalledWith('Home');
|
||||
expect(mockI18n._).toHaveBeenCalledWith('abc123');
|
||||
expect(result).toBe('Accueil');
|
||||
});
|
||||
});
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ export const fromFlatPageLayoutTabToPageLayoutTabDto = (
|
||||
return {
|
||||
...rest,
|
||||
...(overrides ?? {}),
|
||||
overrides,
|
||||
isOverridden: false,
|
||||
createdAt: new Date(createdAt),
|
||||
updatedAt: new Date(updatedAt),
|
||||
|
||||
+12
-2
@@ -1,18 +1,28 @@
|
||||
import { type I18n } from '@lingui/core';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { generateMessageId } from 'src/engine/core-modules/i18n/utils/generateMessageId';
|
||||
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
|
||||
import { type PageLayoutTabOverrides } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
|
||||
|
||||
export const resolvePageLayoutTabTitle = ({
|
||||
title,
|
||||
applicationId,
|
||||
twentyStandardApplicationId,
|
||||
overrides,
|
||||
i18nInstance,
|
||||
}: {
|
||||
title: string;
|
||||
applicationId: string;
|
||||
twentyStandardApplicationId: string;
|
||||
overrides?: PageLayoutTabOverrides | null;
|
||||
i18nInstance: I18n;
|
||||
}): string => {
|
||||
if (applicationId !== TWENTY_STANDARD_APPLICATION.universalIdentifier) {
|
||||
if (applicationId !== twentyStandardApplicationId) {
|
||||
return title;
|
||||
}
|
||||
|
||||
if (isDefined(overrides?.title)) {
|
||||
return title;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user