From 27eb38f5544f88c3306ae01e41ee329bab7b7c48 Mon Sep 17 00:00:00 2001 From: Daniel Krizan Date: Mon, 16 Mar 2026 15:53:59 +0100 Subject: [PATCH 1/3] feat: add branchingEnabled to API key current endpoint Include branchingEnabled boolean in the /v2/api-keys/current response so clients like the chrome extension can conditionally show branch selection UI only when branching is enabled on the project. --- .../kotlin/io/tolgee/api/v2/controllers/ApiKeyController.kt | 4 ++++ .../io/tolgee/hateoas/apiKey/ApiKeyWithLanguagesModel.kt | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/ApiKeyController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/ApiKeyController.kt index 894f38064b3..690a6beb107 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/ApiKeyController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/ApiKeyController.kt @@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.media.Content import io.swagger.v3.oas.annotations.media.Schema import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.tags.Tag +import io.tolgee.constants.Feature import io.tolgee.constants.Message import io.tolgee.dtos.cacheable.isAdmin import io.tolgee.dtos.request.apiKey.CreateApiKeyDto @@ -31,6 +32,7 @@ import io.tolgee.security.authentication.AuthTokenType import io.tolgee.security.authentication.AuthenticationFacade import io.tolgee.security.authentication.RequiresSuperAuthentication import io.tolgee.security.authorization.RequiresProjectPermissions +import io.tolgee.service.project.ProjectFeatureGuard import io.tolgee.service.project.ProjectService import io.tolgee.service.security.ApiKeyService import io.tolgee.service.security.PermissionService @@ -67,6 +69,7 @@ class ApiKeyController( private val pagedResourcesAssembler: PagedResourcesAssembler, private val permissionService: PermissionService, private val simpleProjectModelAssembler: SimpleProjectModelAssembler, + private val projectFeatureGuard: ProjectFeatureGuard, ) { @PostMapping(path = ["/api-keys"]) @Operation(summary = "Create API key", description = "Creates new API key with provided scopes") @@ -133,6 +136,7 @@ class ApiKeyController( return ApiKeyWithLanguagesModel( apiKeyModelAssembler.toModel(apiKey), permittedLanguageIds = translateLanguageIds, + branchingEnabled = projectFeatureGuard.isFeatureEnabled(Feature.BRANCHING, apiKey.project), ) } diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/apiKey/ApiKeyWithLanguagesModel.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/apiKey/ApiKeyWithLanguagesModel.kt index 089b0886f89..debadc2faa1 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/apiKey/ApiKeyWithLanguagesModel.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/apiKey/ApiKeyWithLanguagesModel.kt @@ -14,5 +14,9 @@ open class ApiKeyWithLanguagesModel( deprecated = true, ) val permittedLanguageIds: Set?, + @Schema( + description = "Whether branching is enabled and active on this project.", + ) + val branchingEnabled: Boolean = false, ) : RepresentationModel(), IApiKeyModel by apiKeyModel From 0b150eda2e991e8f83cec051fe2016721b61eb45 Mon Sep 17 00:00:00 2001 From: Daniel Krizan Date: Mon, 16 Mar 2026 16:12:16 +0100 Subject: [PATCH 2/3] fix: use generic error message for project feature guard Rename BRANCHING_NOT_ENABLED_FOR_PROJECT to FEATURE_NOT_ENABLED_FOR_PROJECT and include the feature name as a parameter. Switch from ValidationException to BadRequestException for consistency with OrganizationFeatureGuard. --- backend/data/src/main/kotlin/io/tolgee/constants/Message.kt | 2 +- .../kotlin/io/tolgee/service/project/ProjectFeatureGuard.kt | 6 +++--- webapp/src/translationTools/useErrorTranslation.ts | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/data/src/main/kotlin/io/tolgee/constants/Message.kt b/backend/data/src/main/kotlin/io/tolgee/constants/Message.kt index a3a62e27846..c1be659aced 100644 --- a/backend/data/src/main/kotlin/io/tolgee/constants/Message.kt +++ b/backend/data/src/main/kotlin/io/tolgee/constants/Message.kt @@ -324,7 +324,7 @@ enum class Message { BRANCH_MERGE_REVISION_NOT_VALID, BRANCH_MERGE_CONFLICTS_NOT_RESOLVED, BRANCH_MERGE_ALREADY_MERGED, - BRANCHING_NOT_ENABLED_FOR_PROJECT, + FEATURE_NOT_ENABLED_FOR_PROJECT, EXPORT_KEY_PLURAL_SUFFIX_COLLISION, TRANSLATION_EXCEEDS_CHAR_LIMIT, ; diff --git a/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectFeatureGuard.kt b/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectFeatureGuard.kt index 7f59ebc21e7..bef67cd4ada 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectFeatureGuard.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectFeatureGuard.kt @@ -3,7 +3,7 @@ package io.tolgee.service.project import io.tolgee.component.enabledFeaturesProvider.EnabledFeaturesProvider import io.tolgee.constants.Feature import io.tolgee.constants.Message -import io.tolgee.dtos.request.validators.exceptions.ValidationException +import io.tolgee.exceptions.BadRequestException import io.tolgee.model.Project import io.tolgee.security.ProjectHolder import org.springframework.stereotype.Service @@ -25,7 +25,7 @@ class ProjectFeatureGuard( val project = projectHolder.projectEntity enabledFeaturesProvider.checkFeatureEnabled(project.organizationOwner.id, feature) if (!ProjectFeatureRegistry.isEnabledOnProject(feature, project)) { - throw ValidationException(Message.BRANCHING_NOT_ENABLED_FOR_PROJECT) + throw BadRequestException(Message.FEATURE_NOT_ENABLED_FOR_PROJECT, listOf(feature.name)) } } @@ -35,7 +35,7 @@ class ProjectFeatureGuard( ) { enabledFeaturesProvider.checkFeatureEnabled(project.organizationOwner.id, feature) if (!ProjectFeatureRegistry.isEnabledOnProject(feature, project)) { - throw ValidationException(Message.BRANCHING_NOT_ENABLED_FOR_PROJECT) + throw BadRequestException(Message.FEATURE_NOT_ENABLED_FOR_PROJECT, listOf(feature.name)) } } diff --git a/webapp/src/translationTools/useErrorTranslation.ts b/webapp/src/translationTools/useErrorTranslation.ts index fe684582930..cef38e70e2d 100644 --- a/webapp/src/translationTools/useErrorTranslation.ts +++ b/webapp/src/translationTools/useErrorTranslation.ts @@ -213,6 +213,10 @@ export function useErrorTranslation() { return t('operation_not_permitted_in_read_only_mode'); case 'cannot_delete_branch_with_children': return t('cannot_delete_branch_with_children'); + case 'feature_not_enabled_for_project': + return t('feature_not_enabled_for_project', { + feature: params?.[0] || '', + }); case 'export_key_plural_suffix_collision': return t('export_key_plural_suffix_collision', { pluralKey: params?.[0] || '', From 9fb71b3ddb2e7eb8d5809604ff3e7f33fbc1e575 Mon Sep 17 00:00:00 2001 From: Daniel Krizan Date: Mon, 16 Mar 2026 19:18:23 +0100 Subject: [PATCH 3/3] chore: schema regenerated --- webapp/src/service/apiSchema.generated.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/webapp/src/service/apiSchema.generated.ts b/webapp/src/service/apiSchema.generated.ts index 6223e7785b5..8f19d37f6ad 100644 --- a/webapp/src/service/apiSchema.generated.ts +++ b/webapp/src/service/apiSchema.generated.ts @@ -1358,6 +1358,8 @@ export interface components { viewLanguageIds?: number[]; }; ApiKeyWithLanguagesModel: { + /** @description Whether branching is enabled and active on this project. */ + branchingEnabled: boolean; description: string; /** Format: int64 */ expiresAt?: number; @@ -2942,7 +2944,7 @@ export interface components { | "branch_merge_revision_not_valid" | "branch_merge_conflicts_not_resolved" | "branch_merge_already_merged" - | "branching_not_enabled_for_project" + | "feature_not_enabled_for_project" | "export_key_plural_suffix_collision" | "translation_exceeds_char_limit"; params?: unknown[]; @@ -6378,7 +6380,7 @@ export interface components { | "branch_merge_revision_not_valid" | "branch_merge_conflicts_not_resolved" | "branch_merge_already_merged" - | "branching_not_enabled_for_project" + | "feature_not_enabled_for_project" | "export_key_plural_suffix_collision" | "translation_exceeds_char_limit"; params?: unknown[];