Fix zh-CN sidebar translations in minimalMetadata API (#19342)

The minimalMetadata query was returning untranslated English labels for
standard objects (Companies, People, Opportunities, etc.) when users
requested zh-CN locale. This fix adds translation logic to
MinimalMetadataService using the existing I18nService.

Changes:
- Add translateLabel() method to translate standard object labels
- Pass locale from GraphQL context through resolver to service
- Only translate non-custom objects (custom objects use user-defined
labels)

Co-authored-by: Kevin Yu <kevin.yu@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
yukuotec
2026-04-06 02:14:20 +08:00
committed by GitHub
parent ed912ec548
commit 265b2582ee
3 changed files with 159 additions and 13 deletions
@@ -1,7 +1,8 @@
import { UseGuards } from '@nestjs/common';
import { Query } from '@nestjs/graphql';
import { Context, Query } from '@nestjs/graphql';
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
import { type I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
@@ -22,10 +23,12 @@ export class MinimalMetadataResolver {
@AuthWorkspace() workspace: WorkspaceEntity,
@AuthUserWorkspaceId({ allowUndefined: true })
userWorkspaceId: string | undefined,
@Context() context: I18nContext,
): Promise<MinimalMetadataDTO> {
return this.minimalMetadataService.getMinimalMetadata(
workspace.id,
userWorkspaceId,
context.req.locale,
);
}
}
@@ -4,15 +4,18 @@ import {
ALL_METADATA_NAME,
type AllMetadataName,
} from 'twenty-shared/metadata';
import { type APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
import { ViewVisibility } from 'twenty-shared/types';
import { isDefined, uncapitalize } from 'twenty-shared/utils';
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
import { ALL_FLAT_ENTITY_MAPS_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-flat-entity-maps-properties.constant';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { type CollectionHashDTO } from 'src/engine/metadata-modules/minimal-metadata/dtos/collection-hash.dto';
import { MinimalMetadataDTO } from 'src/engine/metadata-modules/minimal-metadata/dtos/minimal-metadata.dto';
import { MinimalObjectMetadataDTO } from 'src/engine/metadata-modules/minimal-metadata/dtos/minimal-object-metadata.dto';
import { MinimalViewDTO } from 'src/engine/metadata-modules/minimal-metadata/dtos/minimal-view.dto';
import { resolveObjectMetadataStandardOverride } from 'src/engine/metadata-modules/object-metadata/utils/resolve-object-metadata-standard-override.util';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { type WorkspaceCacheKeyName } from 'src/engine/workspace-cache/types/workspace-cache-key.type';
@@ -33,11 +36,13 @@ export class MinimalMetadataService {
constructor(
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly i18nService: I18nService,
) {}
async getMinimalMetadata(
workspaceId: string,
userWorkspaceId?: string,
locale?: string,
): Promise<MinimalMetadataDTO> {
const [{ flatObjectMetadataMaps, flatViewMaps }, cacheHashes] =
await Promise.all([
@@ -63,23 +68,48 @@ export class MinimalMetadataService {
})
.filter(isDefined);
const safeLocale = (locale as keyof typeof APP_LOCALES) ?? SOURCE_LOCALE;
const i18nInstance = this.i18nService.getI18nInstance(safeLocale);
const objectMetadataItems: MinimalObjectMetadataDTO[] = Object.values(
flatObjectMetadataMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter((flatObjectMetadata) => flatObjectMetadata.isActive === true)
.map((flatObjectMetadata) => ({
id: flatObjectMetadata.id,
nameSingular: flatObjectMetadata.nameSingular,
namePlural: flatObjectMetadata.namePlural,
labelSingular: flatObjectMetadata.labelSingular,
labelPlural: flatObjectMetadata.labelPlural,
icon: flatObjectMetadata.icon ?? undefined,
isCustom: flatObjectMetadata.isCustom,
isActive: flatObjectMetadata.isActive,
isSystem: flatObjectMetadata.isSystem,
isRemote: flatObjectMetadata.isRemote,
}));
.map((flatObjectMetadata) => {
const objectMetadataForOverride = {
labelPlural: flatObjectMetadata.labelPlural,
labelSingular: flatObjectMetadata.labelSingular,
description: flatObjectMetadata.description ?? undefined,
icon: flatObjectMetadata.icon ?? undefined,
color: flatObjectMetadata.color ?? undefined,
isCustom: flatObjectMetadata.isCustom,
standardOverrides: flatObjectMetadata.standardOverrides ?? undefined,
};
return {
id: flatObjectMetadata.id,
nameSingular: flatObjectMetadata.nameSingular,
namePlural: flatObjectMetadata.namePlural,
labelSingular: resolveObjectMetadataStandardOverride(
objectMetadataForOverride,
'labelSingular',
safeLocale,
i18nInstance,
),
labelPlural: resolveObjectMetadataStandardOverride(
objectMetadataForOverride,
'labelPlural',
safeLocale,
i18nInstance,
),
icon: flatObjectMetadata.icon ?? undefined,
isCustom: flatObjectMetadata.isCustom,
isActive: flatObjectMetadata.isActive,
isSystem: flatObjectMetadata.isSystem,
isRemote: flatObjectMetadata.isRemote,
};
});
const views: MinimalViewDTO[] = Object.values(
flatViewMaps.byUniversalIdentifier,