Add POC for Field translation (#9898)

Similar to ObjectMetadata translation

Also fixed an issue linked to the migration from `t` to `message`
helper: we're forced to rebuild the ID ourselves
This commit is contained in:
Félix Malfait
2025-01-28 21:25:09 +01:00
committed by GitHub
parent 8754b7107d
commit b1219ff107
13 changed files with 120 additions and 55 deletions

View File

@ -1,5 +1,4 @@
import { defineConfig } from '@lingui/cli'; import { defineConfig } from '@lingui/cli';
import { formatter } from '@lingui/format-po';
export default defineConfig({ export default defineConfig({
sourceLocale: 'en', sourceLocale: 'en',
@ -7,9 +6,6 @@ export default defineConfig({
extractorParserOptions: { extractorParserOptions: {
tsExperimentalDecorators: true, tsExperimentalDecorators: true,
}, },
format: formatter({
explicitIdAsDefault: true,
}),
catalogs: [ catalogs: [
{ {
path: '<rootDir>/src/engine/core-modules/i18n/locales/{locale}', path: '<rootDir>/src/engine/core-modules/i18n/locales/{locale}',

View File

@ -1,33 +1,11 @@
msgid "" msgid ""
msgstr "" msgstr ""
"POT-Creation-Date: 2025-01-25 21:24+0100\n" "POT-Creation-Date: 2025-01-28 21:09+0100\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Generator: @lingui/cli\n" "X-Generator: @lingui/cli\n"
"Language: en\n" "Language: en\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Plural-Forms: \n"
#. js-lingui-explicit-id
#: src/engine/metadata-modules/object-metadata/object-metadata.service.ts:557
#: src/engine/metadata-modules/object-metadata/object-metadata.service.ts:561
msgid "Company"
msgstr "Company"
#. js-lingui-explicit-id
#: src/modules/company/standard-objects/company.workspace-entity.ts:57
#~ msgid "Companies"
#~ msgstr "Companies"
#. js-lingui-explicit-id
#: src/modules/company/standard-objects/company.workspace-entity.ts:58
#~ msgid "A company"
#~ msgstr "A company"
#: src/modules/view/standard-objects/view-field.workspace-entity.ts:32 #: src/modules/view/standard-objects/view-field.workspace-entity.ts:32
msgid "(System) View Fields" msgid "(System) View Fields"
@ -61,10 +39,6 @@ msgstr "A company"
msgid "A connected account" msgid "A connected account"
msgstr "A connected account" msgstr "A connected account"
#: src/modules/favorite/standard-objects/favorite.workspace-entity.ts:37
#~ msgid "A favorite"
#~ msgstr "A favorite"
#: src/modules/favorite/standard-objects/favorite.workspace-entity.ts:37 #: src/modules/favorite/standard-objects/favorite.workspace-entity.ts:37
msgid "A favorite that can be accessed from the left menu" msgid "A favorite that can be accessed from the left menu"
msgstr "A favorite that can be accessed from the left menu" msgstr "A favorite that can be accessed from the left menu"
@ -149,10 +123,6 @@ msgstr "An event related to user behavior"
msgid "An opportunity" msgid "An opportunity"
msgstr "An opportunity" msgstr "An opportunity"
#: src/modules/task/standard-objects/task-target.workspace-entity.ts:27
#~ msgid "An task target"
#~ msgstr "An task target"
#: src/modules/api-key/standard-objects/api-key.workspace-entity.ts:17 #: src/modules/api-key/standard-objects/api-key.workspace-entity.ts:17
msgid "API Key" msgid "API Key"
msgstr "API Key" msgstr "API Key"
@ -308,6 +278,10 @@ msgstr "Message Threads"
msgid "Messages" msgid "Messages"
msgstr "Messages" msgstr "Messages"
#: src/modules/company/standard-objects/company.workspace-entity.ts:67
msgid "Name"
msgstr "Name"
#: src/modules/note/standard-objects/note.workspace-entity.ts:46 #: src/modules/note/standard-objects/note.workspace-entity.ts:46
msgid "Note" msgid "Note"
msgstr "Note" msgstr "Note"
@ -356,6 +330,10 @@ msgstr "Task Targets"
msgid "Tasks" msgid "Tasks"
msgstr "Tasks" msgstr "Tasks"
#: src/modules/company/standard-objects/company.workspace-entity.ts:68
msgid "The company name"
msgstr "The company name"
#: src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts:34 #: src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts:34
msgid "Timeline Activities" msgid "Timeline Activities"
msgstr "Timeline Activities" msgstr "Timeline Activities"

View File

@ -1,6 +1,6 @@
msgid "" msgid ""
msgstr "" msgstr ""
"POT-Creation-Date: 2025-01-28 09:39+0100\n" "POT-Creation-Date: 2025-01-28 21:09+0100\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -208,12 +208,6 @@ msgstr "Entreprises"
msgid "Company" msgid "Company"
msgstr "Entreprise" msgstr "Entreprise"
#. js-lingui-explicit-id
#: src/engine/metadata-modules/object-metadata/object-metadata.service.ts:557
#: src/engine/metadata-modules/object-metadata/object-metadata.service.ts:561
msgid "Company"
msgstr "Entreprise"
#: src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts:33 #: src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts:33
msgid "Connected Account" msgid "Connected Account"
msgstr "Compte connecté" msgstr "Compte connecté"
@ -284,6 +278,10 @@ msgstr "Fils de messages"
msgid "Messages" msgid "Messages"
msgstr "Messages" msgstr "Messages"
#: src/modules/company/standard-objects/company.workspace-entity.ts:67
msgid "Name"
msgstr "Nom"
#: src/modules/note/standard-objects/note.workspace-entity.ts:46 #: src/modules/note/standard-objects/note.workspace-entity.ts:46
msgid "Note" msgid "Note"
msgstr "Note" msgstr "Note"
@ -332,6 +330,10 @@ msgstr "Objectifs de la tâche"
msgid "Tasks" msgid "Tasks"
msgstr "Tâches" msgstr "Tâches"
#: src/modules/company/standard-objects/company.workspace-entity.ts:68
msgid "The company name"
msgstr "Le nom de l'entreprise"
#: src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts:34 #: src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts:34
msgid "Timeline Activities" msgid "Timeline Activities"
msgstr "Calendrier des activités" msgstr "Calendrier des activités"

View File

@ -1 +1 @@
/*eslint-disable*/module.exports={messages:JSON.parse("{\"Company\":[\"Company\"],\"Companies\":[\"Companies\"],\"A company\":[\"A company\"],\"(System) View Fields\":[\"(System) View Fields\"],\"(System) View Filter Groups\":[\"(System) View Filter Groups\"],\"(System) View Filters\":[\"(System) View Filters\"],\"(System) View Groups\":[\"(System) View Groups\"],\"(System) View Sorts\":[\"(System) View Sorts\"],\"(System) Views\":[\"(System) Views\"],\"A connected account\":[\"A connected account\"],\"A favorite\":[\"A favorite\"],\"A favorite that can be accessed from the left menu\":[\"A favorite that can be accessed from the left menu\"],\"A Folder of favorites\":[\"A Folder of favorites\"],\"A group of related messages (e.g. email thread, chat thread)\":[\"A group of related messages (e.g. email thread, chat thread)\"],\"A message sent or received through a messaging channel (email, chat, etc.)\":[\"A message sent or received through a messaging channel (email, chat, etc.)\"],\"A note\":[\"A note\"],\"A note target\":[\"A note target\"],\"A person\":[\"A person\"],\"A task\":[\"A task\"],\"A task target\":[\"A task target\"],\"A webhook\":[\"A webhook\"],\"A workflow\":[\"A workflow\"],\"A workflow event listener\":[\"A workflow event listener\"],\"A workflow run\":[\"A workflow run\"],\"A workflow version\":[\"A workflow version\"],\"A workspace member\":[\"A workspace member\"],\"Aggregated / filtered event to be displayed on the timeline\":[\"Aggregated / filtered event to be displayed on the timeline\"],\"An API key\":[\"An API key\"],\"An attachment\":[\"An attachment\"],\"An audit log of actions performed in the system\":[\"An audit log of actions performed in the system\"],\"An event related to user behavior\":[\"An event related to user behavior\"],\"An opportunity\":[\"An opportunity\"],\"An task target\":[\"An task target\"],\"API Key\":[\"API Key\"],\"API Keys\":[\"API Keys\"],\"Attachment\":[\"Attachment\"],\"Attachments\":[\"Attachments\"],\"Audit Log\":[\"Audit Log\"],\"Audit Logs\":[\"Audit Logs\"],\"Behavioral Event\":[\"Behavioral Event\"],\"Behavioral Events\":[\"Behavioral Events\"],\"Blocklist\":[\"Blocklist\"],\"Blocklists\":[\"Blocklists\"],\"Calendar Channel\":[\"Calendar Channel\"],\"Calendar Channel Event Association\":[\"Calendar Channel Event Association\"],\"Calendar Channel Event Associations\":[\"Calendar Channel Event Associations\"],\"Calendar Channels\":[\"Calendar Channels\"],\"Calendar event\":[\"Calendar event\"],\"Calendar event participant\":[\"Calendar event participant\"],\"Calendar event participants\":[\"Calendar event participants\"],\"Calendar events\":[\"Calendar events\"],\"Connected Account\":[\"Connected Account\"],\"Connected Accounts\":[\"Connected Accounts\"],\"Favorite\":[\"Favorite\"],\"Favorite Folder\":[\"Favorite Folder\"],\"Favorite Folders\":[\"Favorite Folders\"],\"Favorites\":[\"Favorites\"],\"Message\":[\"Message\"],\"Message Channel\":[\"Message Channel\"],\"Message Channel Message Association\":[\"Message Channel Message Association\"],\"Message Channel Message Associations\":[\"Message Channel Message Associations\"],\"Message Channels\":[\"Message Channels\"],\"Message Participant\":[\"Message Participant\"],\"Message Participants\":[\"Message Participants\"],\"Message Synced with a Message Channel\":[\"Message Synced with a Message Channel\"],\"Message Thread\":[\"Message Thread\"],\"Message Threads\":[\"Message Threads\"],\"Messages\":[\"Messages\"],\"Note\":[\"Note\"],\"Note Target\":[\"Note Target\"],\"Note Targets\":[\"Note Targets\"],\"Notes\":[\"Notes\"],\"Opportunities\":[\"Opportunities\"],\"Opportunity\":[\"Opportunity\"],\"People\":[\"People\"],\"Person\":[\"Person\"],\"Task\":[\"Task\"],\"Task Target\":[\"Task Target\"],\"Task Targets\":[\"Task Targets\"],\"Tasks\":[\"Tasks\"],\"Timeline Activities\":[\"Timeline Activities\"],\"Timeline Activity\":[\"Timeline Activity\"],\"View\":[\"View\"],\"View Field\":[\"View Field\"],\"View Fields\":[\"View Fields\"],\"View Filter\":[\"View Filter\"],\"View Filter Group\":[\"View Filter Group\"],\"View Filter Groups\":[\"View Filter Groups\"],\"View Filters\":[\"View Filters\"],\"View Group\":[\"View Group\"],\"View Groups\":[\"View Groups\"],\"View Sort\":[\"View Sort\"],\"View Sorts\":[\"View Sorts\"],\"Views\":[\"Views\"],\"Webhook\":[\"Webhook\"],\"Webhooks\":[\"Webhooks\"],\"Workflow\":[\"Workflow\"],\"Workflow Run\":[\"Workflow Run\"],\"Workflow Runs\":[\"Workflow Runs\"],\"Workflow Version\":[\"Workflow Version\"],\"Workflow Versions\":[\"Workflow Versions\"],\"WorkflowEventListener\":[\"WorkflowEventListener\"],\"WorkflowEventListeners\":[\"WorkflowEventListeners\"],\"Workflows\":[\"Workflows\"],\"Workspace Member\":[\"Workspace Member\"],\"Workspace Members\":[\"Workspace Members\"]}")}; /*eslint-disable*/module.exports={messages:JSON.parse("{\"Qyrd7v\":[\"(System) View Fields\"],\"9Y3fTB\":[\"(System) View Filter Groups\"],\"TB2jLV\":[\"(System) View Filters\"],\"Y7M7Ro\":[\"(System) View Groups\"],\"9vliLw\":[\"(System) View Sorts\"],\"5B59WE\":[\"(System) Views\"],\"kZR6+h\":[\"A company\"],\"+aeifv\":[\"A connected account\"],\"HCoswz\":[\"A favorite that can be accessed from the left menu\"],\"6w8bHl\":[\"A Folder of favorites\"],\"sSGYmf\":[\"A group of related messages (e.g. email thread, chat thread)\"],\"vZj1Xc\":[\"A message sent or received through a messaging channel (email, chat, etc.)\"],\"bufuBA\":[\"A note\"],\"6kUkZW\":[\"A note target\"],\"Io42ej\":[\"A person\"],\"mkFXEH\":[\"A task\"],\"hk2NzW\":[\"A task target\"],\"HTSJFW\":[\"A webhook\"],\"ZIN9Ga\":[\"A workflow\"],\"juBVjt\":[\"A workflow event listener\"],\"1+xDbI\":[\"A workflow run\"],\"N0g7rp\":[\"A workflow version\"],\"HpZ/I5\":[\"A workspace member\"],\"W58PBh\":[\"Aggregated / filtered event to be displayed on the timeline\"],\"qeHcQj\":[\"An API key\"],\"MjyFvC\":[\"An attachment\"],\"+bL++X\":[\"An audit log of actions performed in the system\"],\"muVHgL\":[\"An event related to user behavior\"],\"bZq8rL\":[\"An opportunity\"],\"yRnk5W\":[\"API Key\"],\"FfSJ1Y\":[\"API Keys\"],\"UY1vmE\":[\"Attachment\"],\"w/Sphq\":[\"Attachments\"],\"ilRCh1\":[\"Audit Log\"],\"EPEFrH\":[\"Audit Logs\"],\"20B9kW\":[\"Behavioral Event\"],\"Jeh/Q/\":[\"Behavioral Events\"],\"K1172m\":[\"Blocklist\"],\"L5JhJe\":[\"Blocklists\"],\"Nh6GTX\":[\"Calendar Channel\"],\"jfNQ0m\":[\"Calendar Channel Event Association\"],\"kYNT3F\":[\"Calendar Channel Event Associations\"],\"Znix/S\":[\"Calendar Channels\"],\"bRk+FR\":[\"Calendar event\"],\"N2kMfO\":[\"Calendar event participant\"],\"AWDqkQ\":[\"Calendar event participants\"],\"X9A2xC\":[\"Calendar events\"],\"s2QZS6\":[\"Companies\"],\"7i8j3G\":[\"Company\"],\"PQ1Dw2\":[\"Connected Account\"],\"AMDUqA\":[\"Connected Accounts\"],\"6Ki4Pv\":[\"Favorite\"],\"TDlZ/o\":[\"Favorite Folder\"],\"SStz54\":[\"Favorite Folders\"],\"X9kySA\":[\"Favorites\"],\"xDAtGP\":[\"Message\"],\"g+QGD6\":[\"Message Channel\"],\"disipM\":[\"Message Channel Message Association\"],\"ijQY3P\":[\"Message Channel Message Associations\"],\"k7LXPQ\":[\"Message Channels\"],\"IUmVwu\":[\"Message Participant\"],\"FhIFx7\":[\"Message Participants\"],\"IC5A8V\":[\"Message Synced with a Message Channel\"],\"de2nM/\":[\"Message Thread\"],\"RD0ecC\":[\"Message Threads\"],\"t7TeQU\":[\"Messages\"],\"6YtxFj\":[\"Name\"],\"KiJn9B\":[\"Note\"],\"spaO7l\":[\"Note Target\"],\"tD4BxK\":[\"Note Targets\"],\"1DBGsz\":[\"Notes\"],\"4MyDFl\":[\"Opportunities\"],\"SV/iis\":[\"Opportunity\"],\"1wdjme\":[\"People\"],\"OZdaTZ\":[\"Person\"],\"Q3P/4s\":[\"Task\"],\"WSiiWf\":[\"Task Target\"],\"836FiO\":[\"Task Targets\"],\"GtycJ/\":[\"Tasks\"],\"N31Pso\":[\"The company name\"],\"az1boY\":[\"Timeline Activities\"],\"K/kU4E\":[\"Timeline Activity\"],\"jpctdh\":[\"View\"],\"cZPDyy\":[\"View Field\"],\"GUFYyq\":[\"View Fields\"],\"JRtI7Y\":[\"View Filter\"],\"l9/6pD\":[\"View Filter Group\"],\"/aP3iG\":[\"View Filter Groups\"],\"vj5JsR\":[\"View Filters\"],\"ziEP12\":[\"View Group\"],\"V4nZs/\":[\"View Groups\"],\"EUjpwJ\":[\"View Sort\"],\"UsdY3K\":[\"View Sorts\"],\"1I6UoR\":[\"Views\"],\"TRDppN\":[\"Webhook\"],\"v1kQyJ\":[\"Webhooks\"],\"bLt/0J\":[\"Workflow\"],\"5vIcqC\":[\"Workflow Run\"],\"u6DF/V\":[\"Workflow Runs\"],\"+wYPET\":[\"Workflow Version\"],\"OCyhkn\":[\"Workflow Versions\"],\"ENOy6I\":[\"WorkflowEventListener\"],\"3JA9se\":[\"WorkflowEventListeners\"],\"woYYQq\":[\"Workflows\"],\"qc38qR\":[\"Workspace Member\"],\"YCAEr+\":[\"Workspace Members\"]}")};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
export type I18nContext = {
req: {
headers: {
'x-locale': string | undefined;
};
};
};

View File

@ -0,0 +1,11 @@
import crypto from 'crypto';
const UNIT_SEPARATOR = '\u001F';
export function generateMessageId(msg: string, context = '') {
return crypto
.createHash('sha256')
.update(msg + UNIT_SEPARATOR + (context || ''))
.digest('base64')
.slice(0, 6);
}

View File

@ -16,6 +16,7 @@ import { FieldMetadataType } from 'twenty-shared';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface'; import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
@ -43,6 +44,30 @@ export class FieldMetadataResolver {
private readonly featureFlagService: FeatureFlagService, private readonly featureFlagService: FeatureFlagService,
) {} ) {}
@ResolveField(() => String, { nullable: true })
async label(
@Parent() fieldMetadata: FieldMetadataDTO,
@Context() context: I18nContext,
): Promise<string> {
return this.fieldMetadataService.resolveTranslatableString(
fieldMetadata,
'label',
context.req.headers['x-locale'],
);
}
@ResolveField(() => String, { nullable: true })
async description(
@Parent() fieldMetadata: FieldMetadataDTO,
@Context() context: I18nContext,
): Promise<string> {
return this.fieldMetadataService.resolveTranslatableString(
fieldMetadata,
'description',
context.req.headers['x-locale'],
);
}
@Mutation(() => FieldMetadataDTO) @Mutation(() => FieldMetadataDTO)
async createOneField( async createOneField(
@Args('input') input: CreateOneFieldMetadataInput, @Args('input') input: CreateOneFieldMetadataInput,

View File

@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { i18n } from '@lingui/core';
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import isEmpty from 'lodash.isempty'; import isEmpty from 'lodash.isempty';
import { FieldMetadataType } from 'twenty-shared'; import { FieldMetadataType } from 'twenty-shared';
@ -8,6 +9,7 @@ import { DataSource, FindOneOptions, Repository } from 'typeorm';
import { v4 as uuidV4, v4 } from 'uuid'; import { v4 as uuidV4, v4 } from 'uuid';
import { TypeORMService } from 'src/database/typeorm/typeorm.service'; import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { generateMessageId } from 'src/engine/core-modules/i18n/utils/generateMessageId';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
@ -773,4 +775,29 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
return fieldMetadataInput; return fieldMetadataInput;
} }
async resolveTranslatableString(
fieldMetadata: FieldMetadataDTO,
labelKey: 'label' | 'description',
locale: string | undefined,
): Promise<string> {
if (fieldMetadata.isCustom) {
return fieldMetadata[labelKey] ?? '';
}
if (!locale) {
return fieldMetadata[labelKey] ?? '';
}
i18n.activate(locale);
const messageId = generateMessageId(fieldMetadata[labelKey] ?? '');
const translatedMessage = i18n._(messageId);
if (translatedMessage === messageId) {
return fieldMetadata[labelKey] ?? '';
}
return translatedMessage;
}
} }

View File

@ -8,6 +8,7 @@ import {
Resolver, Resolver,
} from '@nestjs/graphql'; } from '@nestjs/graphql';
import { I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
@ -32,7 +33,7 @@ export class ObjectMetadataResolver {
@ResolveField(() => String, { nullable: true }) @ResolveField(() => String, { nullable: true })
async labelPlural( async labelPlural(
@Parent() objectMetadata: ObjectMetadataDTO, @Parent() objectMetadata: ObjectMetadataDTO,
@Context() context, @Context() context: I18nContext,
): Promise<string> { ): Promise<string> {
return this.objectMetadataService.resolveTranslatableString( return this.objectMetadataService.resolveTranslatableString(
objectMetadata, objectMetadata,
@ -44,7 +45,7 @@ export class ObjectMetadataResolver {
@ResolveField(() => String, { nullable: true }) @ResolveField(() => String, { nullable: true })
async labelSingular( async labelSingular(
@Parent() objectMetadata: ObjectMetadataDTO, @Parent() objectMetadata: ObjectMetadataDTO,
@Context() context, @Context() context: I18nContext,
): Promise<string> { ): Promise<string> {
return this.objectMetadataService.resolveTranslatableString( return this.objectMetadataService.resolveTranslatableString(
objectMetadata, objectMetadata,
@ -56,7 +57,7 @@ export class ObjectMetadataResolver {
@ResolveField(() => String, { nullable: true }) @ResolveField(() => String, { nullable: true })
async description( async description(
@Parent() objectMetadata: ObjectMetadataDTO, @Parent() objectMetadata: ObjectMetadataDTO,
@Context() context, @Context() context: I18nContext,
): Promise<string> { ): Promise<string> {
return this.objectMetadataService.resolveTranslatableString( return this.objectMetadataService.resolveTranslatableString(
objectMetadata, objectMetadata,

View File

@ -11,6 +11,7 @@ import { FindManyOptions, FindOneOptions, In, Not, Repository } from 'typeorm';
import { ObjectMetadataStandardIdToIdMap } from 'src/engine/metadata-modules/object-metadata/interfaces/object-metadata-standard-id-to-id-map'; import { ObjectMetadataStandardIdToIdMap } from 'src/engine/metadata-modules/object-metadata/interfaces/object-metadata-standard-id-to-id-map';
import { generateMessageId } from 'src/engine/core-modules/i18n/utils/generateMessageId';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service'; import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service';
@ -539,14 +540,18 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
async resolveTranslatableString( async resolveTranslatableString(
objectMetadata: ObjectMetadataDTO, objectMetadata: ObjectMetadataDTO,
labelKey: 'labelPlural' | 'labelSingular' | 'description', labelKey: 'labelPlural' | 'labelSingular' | 'description',
locale: string, locale: string | undefined,
): Promise<string> { ): Promise<string> {
if (objectMetadata.isCustom) { if (objectMetadata.isCustom) {
return objectMetadata[labelKey]; return objectMetadata[labelKey];
} }
if (!locale) {
return objectMetadata[labelKey];
}
i18n.activate(locale); i18n.activate(locale);
return i18n._(objectMetadata[labelKey]); return i18n._(generateMessageId(objectMetadata[labelKey]));
} }
} }

View File

@ -1,3 +1,4 @@
import { MessageDescriptor } from '@lingui/core';
import { FieldMetadataType } from 'twenty-shared'; import { FieldMetadataType } from 'twenty-shared';
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface'; import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
@ -14,8 +15,14 @@ export interface WorkspaceFieldOptions<
> { > {
standardId: string; standardId: string;
type: T; type: T;
label: string | ((objectMetadata: ObjectMetadataEntity) => string); label:
description?: string | ((objectMetadata: ObjectMetadataEntity) => string); | string
| MessageDescriptor
| ((objectMetadata: ObjectMetadataEntity) => string);
description?:
| string
| MessageDescriptor
| ((objectMetadata: ObjectMetadataEntity) => string);
icon?: string; icon?: string;
defaultValue?: FieldMetadataDefaultValue<T>; defaultValue?: FieldMetadataDefaultValue<T>;
options?: FieldMetadataOptions<T>; options?: FieldMetadataOptions<T>;
@ -72,9 +79,15 @@ export function WorkspaceField<T extends FieldMetadataType>(
target: object.constructor, target: object.constructor,
standardId: options.standardId, standardId: options.standardId,
name: propertyKey.toString(), name: propertyKey.toString(),
label: options.label, label:
typeof options.label === 'object'
? (options.label.message ?? '')
: options.label,
type: options.type, type: options.type,
description: options.description, description:
typeof options.description === 'object'
? (options.description.message ?? '')
: options.description,
icon: options.icon, icon: options.icon,
defaultValue, defaultValue,
options: options.options, options: options.options,

View File

@ -64,8 +64,8 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
@WorkspaceField({ @WorkspaceField({
standardId: COMPANY_STANDARD_FIELD_IDS.name, standardId: COMPANY_STANDARD_FIELD_IDS.name,
type: FieldMetadataType.TEXT, type: FieldMetadataType.TEXT,
label: 'Name', label: msg`Name`,
description: 'The company name', description: msg`The company name`,
icon: 'IconBuildingSkyscraper', icon: 'IconBuildingSkyscraper',
}) })
name: string; name: string;