Skip to content

feat: translate quick start#8875

Open
mikeallisonJS wants to merge 26 commits intomainfrom
26-00-MA-feat-translate-quick-start
Open

feat: translate quick start#8875
mikeallisonJS wants to merge 26 commits intomainfrom
26-00-MA-feat-translate-quick-start

Conversation

@mikeallisonJS
Copy link
Collaborator

@mikeallisonJS mikeallisonJS commented Mar 18, 2026

Summary by CodeRabbit

  • New Features

    • AI-powered translation now includes journey customization fields and descriptions, with a "Translate with AI" option and progress UI.
  • Improvements

    • Better language selection and fallback handling for customization translations; multiselect labels and additional block fields are translatable.
    • Preserves template placeholders ({{ }}); streamed translations apply per-item with isolated error handling and finer-grained progress.
  • Tests

    • Expanded unit and integration tests covering translation scenarios and edge cases.
  • Localization

    • Added UI strings for translation controls and prompts.

Kneesal and others added 15 commits November 17, 2025 02:13
- Added .cursorignore to exclude environment files.
- Updated .gitignore to include new personal Claude rules and Strapi CMS config files.
- Modified .prettierignore to include Kubernetes manifests.
- Updated package.json with new dependencies and version upgrades.
- Added new rules and guidelines for backend, frontend, and infrastructure in .claude directory.
- Updated workflows to improve dependency installation and notifications.
- Adjusted TypeScript configuration for better module resolution.
…put structure

- Replaced instances of generateObject with generateText in translation logic.
- Adjusted mock implementations and test cases to reflect the new function usage.
- Updated return values to align with the new Output structure in the translation schema.
- Enhanced error handling in LanguageScreen for guest and signed-in users during journey duplication.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Switches AI calls to generateText/streamText with Output wrappers, adds translateCustomizationFields (implementation + tests), changes streamed block validation/update flow, includes customization fields in queries/subscriptions/cache, adds AI translation UI and language selection, and upgrades Gemini model + SDK versions.

Changes

Cohort / File(s) Summary
Core translation service & tests
apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.ts, apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.spec.ts
Replaced generateObject/streamObject with generateText/streamText + Output wrappers; migrated to gemini-2.5-flash; reworked streamed element handling with per-item validation/try-catch, new BlockTranslation schemas, allowed-field maps, and updated prompt/schema usage.
Customization translation module & tests
apis/api-journeys-modern/src/schema/journeyAiTranslate/translateCustomizationFields/translateCustomizationFields.ts, .../index.ts, .../translateCustomizationFields.spec.ts
New exported translateCustomizationFields implementation translating description (preserving {{ }}) and field value/defaultValue via AI; Zod output schemas; comprehensive Jest tests covering null/empty cases, placeholder preservation, language fallback, and prompt assertions.
Language detection & shared AI updates
apis/api-journeys-modern/src/schema/journeyLanguageAiDetect/journeyLanguageAiDetect.ts, libs/shared/ai/src/getImageDescription/getImageDescription.ts
Switched to generateText + Output.object usage and updated destructuring from objectoutput; bumped Gemini model to gemini-2.5-flash.
GraphQL schema & subscription/cache changes
apis/api-journeys-modern/schema.graphql, apis/api-gateway/schema.graphql, libs/journeys/ui/src/libs/useJourneyAiTranslateSubscription/useJourneyAiTranslateSubscription.ts
Added optional userLanguageId/userLanguageName to JourneyAiTranslateInput; subscription now accepts/forwards those variables and returns journeyCustomizationDescription + journeyCustomizationFields; Apollo cache writeFragment updated to persist these fields.
Admin UI: translation flows & dialogs
apps/journeys-admin/src/components/TemplateCustomization/.../LanguageScreen/LanguageScreen.tsx, apps/journeys-admin/src/components/JourneyList/.../TranslateJourneyDialog/TranslateJourneyDialog.tsx, apps/journeys-admin/src/components/Team/.../CopyToTeamMenuItem/CopyToTeamMenuItem.tsx
Added "Translate with AI" toggle, translation-language selection/validation, start subscription flow (duplication + subscription variables including userLanguage*), progress UI, and subscription completion/error handling; internal state extended to carry userLanguageId/userLanguageName.
UI components & template extraction
apps/journeys-admin/src/components/Editor/.../MultiselectOptionEdit/MultiselectOptionEdit.tsx, libs/journeys/ui/src/components/MultiselectOption/MultiselectOption.tsx, apps/journeys-admin/src/.../getTemplateCustomizationFields/getTemplateCustomizationFields.ts
Resolve labels via journey customization values when applicable (imports/use of resolve/useGetValueFromJourneyCustomizationString); include MultiselectOptionBlock.label in template customization extraction.
Template/fragment usages & button components
libs/journeys/ui/src/components/TemplateView/CreateJourneyButton/CreateJourneyButton.tsx
Extended internal JourneyForTemplate.language shape to include id, added userLanguageId/userLanguageName into translationVariables initialization for template-based creation flows.
Locales, deps & misc
libs/locales/en/journeys-ui.json, package.json
Added English locale strings for the translation UI and bumped ai and @ai-sdk/google package versions.
Specs & mocks updated
apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.spec.ts, .../translateCustomizationFields/translateCustomizationFields.spec.ts
Updated Jest mocks from Output.object/Output.array and generateText/streamText shapes; added tests for customization translation, null/empty cases, language fallback, and streaming behaviors.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as Admin UI
    participant Sub as Translate Subscription
    participant API as journeyAiTranslate API
    participant AI as Gemini AI
    participant DB as Database
    participant Cache as Apollo Cache

    User->>UI: Enable "Translate with AI" + pick language
    UI->>UI: Duplicate journey (if signed-in)
    UI->>Sub: Start translation subscription (includes userLanguageName/Id)
    Sub->>API: Run translation flow
    API->>AI: generateText (metadata & customization fields)
    AI-->>API: Output.object/array (translated metadata/fields)
    API->>AI: streamText (elementStream for blocks)
    AI-->>API: Streamed block translation elements (elementStream)
    API->>DB: Update blocks & customization fields (per-item, validated)
    DB-->>API: Confirm updates
    API->>Sub: Emit progress updates + final journey
    Sub->>Cache: writeFragment (journey + customization fields)
    Cache-->>Sub: Cache updated
    Sub->>UI: Notify completion
    UI->>User: Show success & continue
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'feat: translate quick start' is overly broad and vague. It does not clearly convey the specific changes made (AI translation for customization fields, model upgrades, schema updates, and UI enhancements across multiple components). Consider a more descriptive title that captures the main scope, such as 'feat: add AI translation for journey customization fields and blocks' or 'feat: implement customization field translation with improved AI models'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 26-00-MA-feat-translate-quick-start

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@infracost
Copy link

infracost bot commented Mar 18, 2026

💰 Infracost report

Monthly estimate increased by $155 📈

Changed project Baseline cost Usage cost* Total change New monthly cost
infrastructure +$155 - +$155 (+10%) $1,770

*Usage costs can be estimated by updating Infracost Cloud settings, see docs for other options.

Estimate details (includes details of unsupported resources)
Key: * usage cost, ~ changed, + added, - removed

──────────────────────────────────
Project: infrastructure
Module path: infrastructure

~ module.prod.module.eks.aws_eks_node_group.az_2a_ondemand
  +$63 ($125 → $188)

    ~ Instance usage (Linux/UNIX, on-demand, t3.large)
      +$61 ($121 → $182)

    ~ Storage (general purpose SSD, gp2)
      +$2 ($4 → $6)

~ module.stage.module.eks.aws_eks_node_group.az_2a_ondemand
  +$63 ($125 → $188)

    ~ Instance usage (Linux/UNIX, on-demand, t3.large)
      +$61 ($121 → $182)

    ~ Storage (general purpose SSD, gp2)
      +$2 ($4 → $6)

~ module.prod.module.arclight.module.ecs-task.aws_ecs_service.ecs_service
  +$36 ($36 → $72)

    ~ Per GB per hour
      +$6 ($6 → $13)

    ~ Per vCPU per hour
      +$30 ($30 → $59)

+ module.prod.module.cms.module.ecs-task.aws_ecs_service.ecs_service
  +$36

    + Per GB per hour
      +$6

    + Per vCPU per hour
      +$30

+ module.stage.module.cms.module.ecs-task.aws_ecs_service.ecs_service
  +$36

    + Per GB per hour
      +$6

    + Per vCPU per hour
      +$30

~ module.stage.module.api-journeys.module.ecs-task.aws_ecs_service.ecs_service
  -$36 ($72 → $36)

    ~ Per GB per hour
      -$6 ($13 → $6)

    ~ Per vCPU per hour
      -$30 ($59 → $30)

~ module.stage.module.api-gateway-stage.module.ecs-task.aws_ecs_service.ecs_service
  -$43 ($85 → $43)

    ~ Per GB per hour
      -$13 ($26 → $13)

    ~ Per vCPU per hour
      -$30 ($59 → $30)

Monthly cost change for infrastructure (Module path: infrastructure)
Amount:  +$155 ($1,615 → $1,770)
Percent: +10%

──────────────────────────────────
Key: * usage cost, ~ changed, + added, - removed

*Usage costs can be estimated by updating Infracost Cloud settings, see docs for other options.

918 cloud resources were detected:
∙ 126 were estimated
∙ 790 were free
∙ 2 are not supported yet, see https://infracost.io/requested-resources:
  ∙ 1 x aws_s3_bucket_accelerate_configuration
  ∙ 1 x aws_s3_bucket_request_payment_configuration

Infracost estimate: Monthly estimate increased by $155 ↑
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Changed project                                    ┃ Baseline cost ┃ Usage cost* ┃ Total change ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━╋━━━━━━━━━━━━━━┫
┃ infrastructure                                     ┃         +$155 ┃           - ┃ +$155 (+10%) ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━┻━━━━━━━━━━━━━━┛
This comment will be updated when code changes.

@jesus-film-bot
Copy link

Ran Plan for dir: infrastructure workspace: default

Show Output
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
~ update in-place
- destroy
+/- create replacement and then destroy

Terraform will perform the following actions:

  # module.prod.module.api-analytics.module.ecs-task.aws_ecs_service.ecs_service will be updated in-place
~ resource "aws_ecs_service" "ecs_service" {
        id                                 = "arn:aws:ecs:us-east-2:410965620680:service/jfp-ecs-cluster-prod/api-analytics-prod-service"
        name                               = "api-analytics-prod-service"
        tags                               = {}
      ~ task_definition                    = "arn:aws:ecs:us-east-2:410965620680:task-definition/jfp-api-analytics-prod:9" -> (known after apply)
        # (18 unchanged attributes hidden)

        # (4 unchanged blocks hidden)
    }

  # module.prod.module.api-analytics.module.ecs-task.aws_ecs_task_definition.ecs_task_definition must be replaced
+/- resource "aws_ecs_task_definition" "ecs_task_definition" {
      ~ arn                      = "arn:aws:ecs:us-east-2:410965620680:task-definition/jfp-api-analytics-prod:9" -> (known after apply)
      ~ arn_without_revision     = "arn:aws:ecs:us-east-2:410965620680:task-definition/jfp-api-analytics-prod" -> (known after apply)
      ~ container_definitions    = jsonencode(
          ~ [
              ~ {
                    name             = "jfp-api-analytics-prod-app"
                  ~ secrets          = [
                      + {
                          + name      = "DD_API_KEY"
                          + valueFrom = "arn:aws:ssm:us-east-2:410965620680:parameter/terraform/prd/DATADOG_API_KEY"
                        },
                        {
                            name      = "GATEWAY_HMAC_SECRET"
                            valueFrom = "arn:aws:ssm:us-east-2:410965620680:parameter/ecs/api-analytics/prod/GATEWAY_HMAC_SECRET"
                        },
                        # (1 unchanged element hidden)
                        {
                            name      = "PLAUSIBLE_SECRET_KEY_BASE"
                            valueFrom = "arn:aws:ssm:us-east-2:410965620680:parameter/ecs/api-analytics/prod/PLAUSIBLE_SECRET_KEY_BASE"
                        },
                      - {
                          - name      = "PRISMA_LOCATION_ANALYTICS"
                          - valueFrom = "arn:aws:ssm:us-east-2:410965620680:parameter/ecs/api-analytics/prod/PRISMA_LOCATION_ANALYTICS"
                        },
                      - {
                          - name      = "DD_API_KEY"
                          - valueFrom = "arn:aws:ssm:us-east-2:410965620680:parameter/terraform/prd/DATADOG_API_KEY"
                        },
                    ]
                  - systemControls   = []
                    # (9 unchanged attributes hidden)
                },
              ~ {
                  - cpu               = 0
                    name              = "jfp-api-analytics-prod-datadog-agent"
                  - systemControls    = []
                    # (9 unchanged attributes hidden)
                },
              ~ {
                  - cpu                   = 0
                    name                  = "jfp-api-analytics-prod-log-router"
                  - systemControls        = []
                    # (10 unchanged attributes hidden)
                },
            ] # forces replacement
        )
      ~ enable_fault_injection   = false -> (known after apply)
      ~ id                       = "jfp-api-analytics-prod" -> (known after apply)
      ~ revision                 = 9 -> (known after apply)
      - tags                     = {} -> null
      ~ tags_all                 = {} -> (known after apply)
        # (12 unchanged attributes hidden)
    }

  # module.prod.module.api-analytics.module.ecs-task.aws_ssm_parameter.parameters["PRISMA_LOCATION_ANALYTICS"] will be destroyed
  # (because key ["PRISMA_LOCATION_ANALYTICS"] is not in for_each map)
- resource "aws_ssm_parameter" "parameters" {
      - arn             = "arn:aws:ssm:us-east-2:410965620680:parameter/ecs/api-analytics/prod/PRISMA_LOCATION_ANALYTICS" -> null
      - data_type       = "text" -> null
      - id              = "/ecs/api-analytics/prod/PRISMA_LOCATION_ANALYTICS" -> null
      - key_id          = "alias/aws/ssm" -> null
      - name            = "/ecs/api-analytics/prod/PRISMA_LOCATION_ANALYTICS" -> null
      - overwrite       = true -> null
      - region          = "us-east-2" -> null
      - tags            = {
          - "name" = "PRISMA_LOCATION_ANALYTICS"
        } -> null
      - tags_all        = {
          - "name" = "PRISMA_LOCATION_ANALYTICS"
        } -> null
      - tier            = "Standard" -> null
      - type            = "SecureString" -> null
      - value           = (sensitive value) -> null
      - value_wo        = (write-only attribute) -> null
      - version         = 3 -> null
        # (2 unchanged attributes hidden)
    }

  # module.prod.module.api-media.module.ecs-task.aws_appautoscaling_target.service_autoscaling will be updated in-place
~ resource "aws_appautoscaling_target" "service_autoscaling" {
        id                 = "service/jfp-ecs-cluster-prod/api-media-prod-service"
      ~ min_capacity       = 2 -> 1
        tags               = {}
        # (8 unchanged attributes hidden)

        # (1 unchanged block hidden)
    }

  # module.prod.module.api-media.module.ecs-task.aws_ecs_service.ecs_service will be updated in-place
~ resource "aws_ecs_service" "ecs_service" {
      ~ desired_count                      = 2 -> 1
        id                                 = "arn:aws:ecs:us-east-2:410965620680:service/jfp-ecs-cluster-prod/api-media-prod-service"
        name                               = "api-media-prod-service"
        tags                               = {}
        # (18 unchanged attributes hidden)

        # (5 unchanged blocks hidden)
    }

  # module.prod.module.arclight.module.ecs-task.aws_ecs_service.ecs_service will be updated in-place
~ resource "aws_ecs_service" "ecs_service" {
      ~ desired_count                      = 1 -> 2
        id                                 = "arn:aws:ecs:us-east-2:410965620680:service/jfp-ecs-cluster-prod/arclight-prod-service"
        name                               = "arclight-prod-service"
        tags                               = {}
        # (18 unchanged attributes hidden)

        # (4 unchanged blocks hidden)
    }

  # module.stage.module.api-users.module.ecs-task.aws_ecs_service.ecs_service will be updated in-place
~ resource "aws_ecs_service" "ecs_service" {
        id                                 = "arn:aws:ecs:us-east-2:410965620680:service/jfp-ecs-cluster-stage/api-users-stage-service"
        name                               = "api-users-stage-service"
        tags                               = {}
      ~ task_definition                    = "arn:aws:ecs:us-east-2:410965620680:task-definition/jfp-api-users-stage:44" -> (known after apply)
        # (18 unchanged attributes hidden)

        # (4 unchanged blocks hidden)
    }

  # module.stage.module.api-users.module.ecs-task.aws_ecs_task_definition.ecs_task_definition must be replaced
+/- resource "aws_ecs_task_definition" "ecs_task_definition" {
      ~ arn                      = "arn:aws:ecs:us-east-2:410965620680:task-definition/jfp-api-users-stage:44" -> (known after apply)
      ~ arn_without_revision     = "arn:aws:ecs:us-east-2:410965620680:task-definition/jfp-api-users-stage" -> (known after apply)
      ~ container_definitions    = jsonencode(
          ~ [
              ~ {
                    name             = "jfp-api-users-stage-app"
                  ~ secrets          = [
                        # (4 unchanged elements hidden)
                        {
                            name      = "GATEWAY_HMAC_SECRET"
                            valueFrom = "arn:aws:ssm:us-east-2:410965620680:parameter/ecs/api-users/stage/GATEWAY_HMAC_SECRET"
                        },
                      - {
                          - name      = "GATEWAY_URL"
                          - valueFrom = "arn:aws:ssm:us-east-2:410965620680:parameter/ecs/api-users/stage/GATEWAY_URL"
                        },
                        {
                            name      = "GOOGLE_APPLICATION_JSON"
                            valueFrom = "arn:aws:ssm:us-east-2:410965620680:parameter/ecs/api-users/stage/GOOGLE_APPLICATION_JSON"
                        },
                        # (8 unchanged elements hidden)
                    ]
                  - systemControls   = []
                    # (9 unchanged attributes hidden)
                },
              ~ {
                    name              = "jfp-api-users-stage-datadog-agent"
                  - systemControls    = []
                    # (9 unchanged attributes hidden)
                },
              ~ {
                    name                  = "jfp-api-users-stage-log-router"
                  - systemControls        = []
                    # (10 unchanged attributes hidden)
                },
            ] # forces replacement
        )
      ~ enable_fault_injection   = false -> (known after apply)
      ~ id                       = "jfp-api-users-stage" -> (known after apply)
      ~ revision                 = 44 -> (known after apply)
      - tags                     = {} -> null
      ~ tags_all                 = {} -> (known after apply)
        # (12 unchanged attributes hidden)
    }

  # module.stage.module.api-users.module.ecs-task.aws_ssm_parameter.parameters["GATEWAY_URL"] will be destroyed
  # (because key ["GATEWAY_URL"] is not in for_each map)
- resource "aws_ssm_parameter" "parameters" {
      - arn             = "arn:aws:ssm:us-east-2:410965620680:parameter/ecs/api-users/stage/GATEWAY_URL" -> null
      - data_type       = "text" -> null
      - has_value_wo    = false -> null
      - id              = "/ecs/api-users/stage/GATEWAY_URL" -> null
      - key_id          = "alias/aws/ssm" -> null
      - name            = "/ecs/api-users/stage/GATEWAY_URL" -> null
      - overwrite       = true -> null
      - region          = "us-east-2" -> null
      - tags            = {
          - "name" = "GATEWAY_URL"
        } -> null
      - tags_all        = {
          - "name" = "GATEWAY_URL"
        } -> null
      - tier            = "Standard" -> null
      - type            = "SecureString" -> null
      - value           = (sensitive value) -> null
      - value_wo        = (write-only attribute) -> null
      - version         = 3 -> null
        # (2 unchanged attributes hidden)
    }

Plan: 2 to add, 5 to change, 4 to destroy.
╷
│ Warning: Deprecated Resource
│ 
│   with module.datadog.datadog_integration_aws.sandbox,
│   on modules/aws/datadog/main.tf line 118, in resource "datadog_integration_aws" "sandbox":
│  118: resource "datadog_integration_aws" "sandbox" {
│ 
│ **This resource is deprecated - use the `datadog_integration_aws_account`
│ resource instead**:
│ https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/integration_aws_account
╵
╷
│ Warning: Deprecated attribute
│ 
│   on .terraform/modules/datadog.datadog_log_forwarder/modules/log_forwarder/main.tf line 2, in locals:
│    2:   bucket_name = var.bucket_name != "" ? var.bucket_name : "datadog-forwarder-${data.aws_caller_identity.current.account_id}-${data.aws_region.current.name}"
│ 
│ The attribute "name" is deprecated. Refer to the provider documentation for
│ details.
│ 
│ (and 2 more similar warnings elsewhere)
╵
  • ▶️ To apply this plan, comment:
    atlantis apply -d infrastructure
  • 🚮 To delete this plan and lock, click here
  • 🔁 To plan this project again, comment:
    atlantis plan -d infrastructure

Plan: 2 to add, 5 to change, 4 to destroy.


  • ⏩ To apply all unapplied plans from this Pull Request, comment:
    atlantis apply
  • 🚮 To delete all plans and locks from this Pull Request, comment:
    atlantis unlock

… screens

- Added loading states to the Next and Done buttons in various screens (DoneScreen, LanguageScreen, LinksScreen, MediaScreen, SocialScreen, TextScreen) to enhance user experience during asynchronous operations.
- Updated button components to reflect loading states and prevent multiple submissions.
- Ensured that loading states are properly managed in the component state for better UI feedback.
- Removed dotenv and prisma from package.json as they are no longer needed in the project.
@nx-cloud
Copy link

nx-cloud bot commented Mar 18, 2026

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit 24ffb8d

Command Status Duration Result
nx run-many --target=deploy --projects=watch-mo... ❌ Failed 15s View ↗
nx run-many --target=vercel-alias --projects=jo... ✅ Succeeded 2s View ↗
nx run-many --target=upload-sourcemaps --projec... ✅ Succeeded 9s View ↗
nx run-many --target=vercel-alias --projects=watch ✅ Succeeded 2s View ↗
nx run-many --target=upload-sourcemaps --projec... ✅ Succeeded 4s View ↗
nx run-many --target=deploy --projects=journeys... ✅ Succeeded 2m 26s View ↗
nx run-many --target=deploy --projects=watch ✅ Succeeded 1m 56s View ↗
nx run-many --target=vercel-alias --projects=re... ✅ Succeeded 2s View ↗
Additional runs (17) ✅ Succeeded ... View ↗

☁️ Nx Cloud last updated this comment at 2026-03-21 01:03:30 UTC

@nx-cloud
Copy link

nx-cloud bot commented Mar 18, 2026

View your CI Pipeline Execution ↗ for commit c052613

Command Status Duration Result
nx run-many --target=upload-sourcemaps --projec... ✅ Succeeded 3s View ↗
nx run-many --target=upload-sourcemaps --projec... ✅ Succeeded <1s View ↗
nx run-many --target=deploy --projects=docs ✅ Succeeded 37s View ↗
nx run-many --target=deploy --projects=short-links ✅ Succeeded 30s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-19 01:03:29 UTC

@github-actions
Copy link
Contributor

github-actions bot commented Mar 18, 2026

Warnings
⚠️ ❗ Big PR (2395 changes)

(change count - 2395): Pull Request size seems relatively large. If Pull Request contains multiple changes, split each into separate PR will helps faster, easier review.

Generated by 🚫 dangerJS against 24ffb8d

@github-actions github-actions bot temporarily deployed to Preview - watch March 19, 2026 00:00 Inactive
@github-actions github-actions bot temporarily deployed to Preview - resources March 19, 2026 00:00 Inactive
@github-actions github-actions bot temporarily deployed to Preview - watch-modern March 19, 2026 01:01 Inactive
@github-actions github-actions bot temporarily deployed to Preview - resources March 19, 2026 01:01 Inactive
@github-actions github-actions bot temporarily deployed to Preview - videos-admin March 19, 2026 01:01 Inactive
@github-actions github-actions bot temporarily deployed to Preview - journeys March 19, 2026 01:01 Inactive
@github-actions github-actions bot temporarily deployed to Preview - short-links March 19, 2026 01:01 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.ts (1)

449-492: ⚠️ Potential issue | 🟠 Major

Harden the final target-language interpolation in the prompt.

Line 490 uses raw ${input.textLanguageName} while the rest of this prompt uses hardenPrompt(...). That inconsistency reopens prompt-injection risk from user input.

🔧 Suggested fix
-Ensure translations maintain the meaning while being culturally appropriate for ${input.textLanguageName}.
+Ensure translations maintain the meaning while being culturally appropriate for ${hardenPrompt(input.textLanguageName)}.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.ts`
around lines 449 - 492, The prompt concatenation for blockTranslationPrompt uses
raw ${input.textLanguageName} at the end, reintroducing prompt-injection risk;
update the template to pass the target language through the existing
hardenPrompt wrapper (use hardenPrompt(...) around input.textLanguageName) so
the final "Ensure translations..." sentence is constructed with a hardened
string; locate blockTranslationPrompt in journeyAiTranslate.ts and replace the
direct interpolation of input.textLanguageName with the hardenPrompt-wrapped
value wherever it appears in that final sentence.
🧹 Nitpick comments (1)
apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.ts (1)

366-593: Extract shared card-translation logic to prevent drift between subscription and mutation paths.

These two blocks are functionally the same pipeline and are already diverging in small ways. Please move prompt construction + stream handling + validation/update into one shared helper.

Also applies to: 851-1067

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.ts`
around lines 366 - 593, The card translation pipeline in translateCard (prompt
construction, streamText usage, validation and prisma.block.update + in-memory
updatedJourney.blocks updates) is duplicated elsewhere; extract it into a shared
helper (e.g., translateBlocksForCard or translateCardBlocks) that accepts the
card context (cardBlock, cardContent, allBlocksToTranslate, updatedJourney
reference, input, preSystemPrompt, prisma, getValidatedBlockUpdates,
BlockTranslationSchema) and encapsulates building blockTranslationPrompt,
allowedBlockIds set, streaming via streamText(model: google('gemini-2.5-flash'),
Output.array({element: BlockTranslationSchema})), per-item validation using
getValidatedBlockUpdates, DB update via prisma.block.update and in-memory
updatedJourney.blocks merge, and consistent try/catch logging; replace the
existing translateCard logic (and the other duplicate at the other location) to
call this helper with the prepared params. Ensure the helper returns a promise
and preserves the same error handling semantics so both subscription and
mutation paths stay in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.ts`:
- Around line 449-492: The prompt concatenation for blockTranslationPrompt uses
raw ${input.textLanguageName} at the end, reintroducing prompt-injection risk;
update the template to pass the target language through the existing
hardenPrompt wrapper (use hardenPrompt(...) around input.textLanguageName) so
the final "Ensure translations..." sentence is constructed with a hardened
string; locate blockTranslationPrompt in journeyAiTranslate.ts and replace the
direct interpolation of input.textLanguageName with the hardenPrompt-wrapped
value wherever it appears in that final sentence.

---

Nitpick comments:
In
`@apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.ts`:
- Around line 366-593: The card translation pipeline in translateCard (prompt
construction, streamText usage, validation and prisma.block.update + in-memory
updatedJourney.blocks updates) is duplicated elsewhere; extract it into a shared
helper (e.g., translateBlocksForCard or translateCardBlocks) that accepts the
card context (cardBlock, cardContent, allBlocksToTranslate, updatedJourney
reference, input, preSystemPrompt, prisma, getValidatedBlockUpdates,
BlockTranslationSchema) and encapsulates building blockTranslationPrompt,
allowedBlockIds set, streaming via streamText(model: google('gemini-2.5-flash'),
Output.array({element: BlockTranslationSchema})), per-item validation using
getValidatedBlockUpdates, DB update via prisma.block.update and in-memory
updatedJourney.blocks merge, and consistent try/catch logging; replace the
existing translateCard logic (and the other duplicate at the other location) to
call this helper with the prepared params. Ensure the helper returns a promise
and preserves the same error handling semantics so both subscription and
mutation paths stay in sync.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c3582e44-e215-498f-80b0-8370c3f9bfdd

📥 Commits

Reviewing files that changed from the base of the PR and between 48fe116 and c052613.

📒 Files selected for processing (1)
  • apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.ts

- Added optional fields for userLanguageId and userLanguageName to JourneyAiTranslateInput for improved customization during translation.
- Updated related components and tests to support the new fields, ensuring fallback logic for language names is correctly implemented.
- Enhanced GraphQL schema and subscription interfaces to accommodate the new input structure.
- Introduced defaultValueTargetLanguageName to handle default value translations in journey AI.
- Updated translation functions and tests to utilize the new parameter, ensuring fallback to targetLanguageName when not provided.
- Enhanced documentation to clarify the translation behavior for default values.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.spec.ts (1)

963-997: These subscription tests don’t verify the new behavior.

calls.length >= 0 and assertions against a locally constructed fixture will pass even if the subscription never invokes translateCustomizationFields or writes anything to Prisma. Please drive the subscription resolver and assert emitted progress or persistence side effects so these tests actually protect the new flow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.spec.ts`
around lines 963 - 997, The tests currently use weak assertions (calls.length >=
0 and local fixture checks) that don't exercise the subscription resolver;
update the spec to drive the actual subscription flow (call the subscription
resolver used in this suite — e.g., the subscribe/handler that triggers
translateCustomizationFields) and then assert observable side effects: verify
mockTranslateCustomizationFields was called with the journey id and fields,
assert Prisma interactions via prismaMock.journey.update/create were called with
the translatedDescription/translatedFields, and assert emitted subscription
progress events (or resolver-emitted messages) were received; replace the
passive checks around mockTranslateCustomizationFields and the local fixture
with these active calls and assertions to ensure the flow executes.
apis/api-journeys-modern/src/schema/journeyAiTranslate/translateCustomizationFields/translateCustomizationFields.ts (2)

79-88: Consider explicit null check for empty string edge case.

The condition field.value || field.defaultValue will also skip fields where value or defaultValue is an empty string "". If that's intentional (empty strings don't need translation), this is fine. However, if there's a semantic difference between null (not set) and "" (explicitly cleared), you may want explicit null checks instead.

💡 Optional: Explicit null check if empty strings should be preserved
 for (const field of journeyCustomizationFields) {
-  if (field.value || field.defaultValue) {
+  if (field.value != null || field.defaultValue != null) {
     valuesToTranslate.push({
       id: field.id,
       key: field.key,
       value: field.value,
       defaultValue: field.defaultValue
     })
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apis/api-journeys-modern/src/schema/journeyAiTranslate/translateCustomizationFields/translateCustomizationFields.ts`
around lines 79 - 88, The current loop pushing into valuesToTranslate uses a
truthy check (field.value || field.defaultValue) which treats empty strings as
absent; change this to explicit null/undefined checks so empty strings are
preserved: in the loop over journeyCustomizationFields, check (field.value !==
null && field.value !== undefined) || (field.defaultValue !== null &&
field.defaultValue !== undefined) before pushing the object with
id/key/value/defaultValue into valuesToTranslate; update the condition in that
block surrounding the push to use those explicit checks for field.value and
field.defaultValue.

91-118: Consider rate limiting for journeys with many customization fields.

Promise.all executes all field translations in parallel, potentially making up to 2× the number of fields in concurrent AI API calls. If journeys can have many customization fields, this could trigger rate limiting on the Gemini API. For typical use cases with few fields, this is likely fine.

If needed, you could batch translations using a concurrency limiter like p-limit:

import pLimit from 'p-limit'
const limit = pLimit(5) // 5 concurrent calls max

const translatedFields = await Promise.all(
  valuesToTranslate.map((field) => limit(async () => {
    // ... translation logic
  }))
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apis/api-journeys-modern/src/schema/journeyAiTranslate/translateCustomizationFields/translateCustomizationFields.ts`
around lines 91 - 118, Replace the unbounded Promise.all over valuesToTranslate
with a concurrency-limited approach to avoid spiking AI API requests: import
p-limit, create a limiter (e.g., const limit = pLimit(5)), and wrap the async
work for each field (the block that calls translateValue for field.value and
field.defaultValue and returns the object assigned to translatedFields) with
limit(() => ...), so each field's translations run under the limiter (ensuring
translateValue calls for value and defaultValue are executed inside that limited
task); keep using Promise.all on the mapped limited tasks so translatedFields
resolves as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.ts`:
- Around line 313-325: The code sets customizationLanguageName with
input.userLanguageName ?? input.textLanguageName which preserves empty strings;
change the fallback to treat blank/empty userLanguageName as missing (e.g., use
a falsy check like input.userLanguageName?.trim() ? input.userLanguageName :
input.textLanguageName) so translateCustomizationFields receives the
textLanguageName when userLanguageName is '' or whitespace; apply the same
change in the other branch that computes customizationLanguageName (both places
that call translateCustomizationFields) and, if userLanguageId should
independently resolve a name, ensure it is resolved to a non-empty language name
before this fallback logic.

In
`@apis/api-journeys-modern/src/schema/journeyAiTranslate/translateCustomizationFields/translateCustomizationFields.ts`:
- Around line 168-191: Wrap the generateText(...) call that uses Output.object({
schema: CustomizationFieldTranslationSchema }) in a try/catch that specifically
catches the AI SDK's NoOutputGeneratedError (import it from the SDK), so
failures to parse the model response are handled; in the catch block, surface a
clear, contextual error (or return a defined fallback) instead of letting the
code access output.translatedValue when output is undefined, and include any
available raw model response or finishReason in the error/log to aid debugging.

In
`@apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/LanguageScreen/LanguageScreen.tsx`:
- Around line 361-387: The code builds the AI translation prompt using the
current page's journey data (journey.title and journey.language.name) even when
journeyId refers to a different language variant; instead, resolve the actual
source journey from languagesJourneyMap (or the map that links language variant
ids to their journey objects) using journeyId before calling
handleJourneyDuplication and when setting setTranslationVariables, and use that
sourceJourney.title and sourceJourney.language.name (falling back to existing
journey only if not found) for journeyLanguageName and name so the duplicated
journey is populated with the true source template title/language.
- Around line 494-528: The "Translate with AI" checkbox and LanguageAutocomplete
(controlled by values.translateWithAI and values.translateLanguage and using
setFieldValue) must not render for guest quick-starts because the guest submit
path ignores these fields; wrap the existing FormControlLabel and the
conditional LanguageAutocomplete in a guard that detects the guest quick-start
path (e.g., a prop/flag like isGuestQuickStart or isGuest) and only render them
when that flag is false so guests cannot select
translateWithAI/translateLanguage until the guest submit flow supports it.

---

Nitpick comments:
In
`@apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.spec.ts`:
- Around line 963-997: The tests currently use weak assertions (calls.length >=
0 and local fixture checks) that don't exercise the subscription resolver;
update the spec to drive the actual subscription flow (call the subscription
resolver used in this suite — e.g., the subscribe/handler that triggers
translateCustomizationFields) and then assert observable side effects: verify
mockTranslateCustomizationFields was called with the journey id and fields,
assert Prisma interactions via prismaMock.journey.update/create were called with
the translatedDescription/translatedFields, and assert emitted subscription
progress events (or resolver-emitted messages) were received; replace the
passive checks around mockTranslateCustomizationFields and the local fixture
with these active calls and assertions to ensure the flow executes.

In
`@apis/api-journeys-modern/src/schema/journeyAiTranslate/translateCustomizationFields/translateCustomizationFields.ts`:
- Around line 79-88: The current loop pushing into valuesToTranslate uses a
truthy check (field.value || field.defaultValue) which treats empty strings as
absent; change this to explicit null/undefined checks so empty strings are
preserved: in the loop over journeyCustomizationFields, check (field.value !==
null && field.value !== undefined) || (field.defaultValue !== null &&
field.defaultValue !== undefined) before pushing the object with
id/key/value/defaultValue into valuesToTranslate; update the condition in that
block surrounding the push to use those explicit checks for field.value and
field.defaultValue.
- Around line 91-118: Replace the unbounded Promise.all over valuesToTranslate
with a concurrency-limited approach to avoid spiking AI API requests: import
p-limit, create a limiter (e.g., const limit = pLimit(5)), and wrap the async
work for each field (the block that calls translateValue for field.value and
field.defaultValue and returns the object assigned to translatedFields) with
limit(() => ...), so each field's translations run under the limiter (ensuring
translateValue calls for value and defaultValue are executed inside that limited
task); keep using Promise.all on the mapped limited tasks so translatedFields
resolves as before.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 991fa9c6-111e-49e5-be57-0d764ca30a0e

📥 Commits

Reviewing files that changed from the base of the PR and between c052613 and a53b4ab.

⛔ Files ignored due to path filters (5)
  • apps/journeys-admin/__generated__/JourneyAiTranslateCreateSubscription.ts is excluded by !**/__generated__/**
  • apps/journeys/__generated__/JourneyAiTranslateCreateSubscription.ts is excluded by !**/__generated__/**
  • apps/resources/__generated__/JourneyAiTranslateCreateSubscription.ts is excluded by !**/__generated__/**
  • apps/watch/__generated__/JourneyAiTranslateCreateSubscription.ts is excluded by !**/__generated__/**
  • libs/journeys/ui/src/libs/useJourneyAiTranslateSubscription/__generated__/JourneyAiTranslateCreateSubscription.ts is excluded by !**/__generated__/**
📒 Files selected for processing (11)
  • apis/api-gateway/schema.graphql
  • apis/api-journeys-modern/schema.graphql
  • apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.spec.ts
  • apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.ts
  • apis/api-journeys-modern/src/schema/journeyAiTranslate/translateCustomizationFields/translateCustomizationFields.spec.ts
  • apis/api-journeys-modern/src/schema/journeyAiTranslate/translateCustomizationFields/translateCustomizationFields.ts
  • apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/TranslateJourneyDialog/TranslateJourneyDialog.tsx
  • apps/journeys-admin/src/components/Team/CopyToTeamMenuItem/CopyToTeamMenuItem.tsx
  • apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/LanguageScreen/LanguageScreen.tsx
  • libs/journeys/ui/src/components/TemplateView/CreateJourneyButton/CreateJourneyButton.tsx
  • libs/journeys/ui/src/libs/useJourneyAiTranslateSubscription/useJourneyAiTranslateSubscription.ts
✅ Files skipped from review due to trivial changes (1)
  • apis/api-gateway/schema.graphql
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/journeys/ui/src/libs/useJourneyAiTranslateSubscription/useJourneyAiTranslateSubscription.ts

Comment on lines +313 to +325
const customizationLanguageName =
input.userLanguageName ?? input.textLanguageName

// Translate customization fields and description
const customizationTranslation = await translateCustomizationFields({
journeyCustomizationDescription:
journey.journeyCustomizationDescription,
journeyCustomizationFields: journey.journeyCustomizationFields,
sourceLanguageName: input.journeyLanguageName,
targetLanguageName: customizationLanguageName,
defaultValueTargetLanguageName: input.textLanguageName,
journeyAnalysis: analysisResult.analysis
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Treat blank userLanguageName as missing before choosing the customization translation language.

The new callers normalize unresolved language names to '', so input.userLanguageName ?? input.textLanguageName preserves the empty string and translateCustomizationFields gets a blank target language instead of falling back. userLanguageId is also ineffective here unless the caller duplicates the name.

🔧 Minimal fallback fix
-        const customizationLanguageName =
-          input.userLanguageName ?? input.textLanguageName
+        const customizationLanguageName =
+          input.userLanguageName?.trim() || input.textLanguageName

Apply the same change in both branches. If userLanguageId is meant to work independently, resolve it to a name before this fallback.

Also applies to: 824-836

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.ts`
around lines 313 - 325, The code sets customizationLanguageName with
input.userLanguageName ?? input.textLanguageName which preserves empty strings;
change the fallback to treat blank/empty userLanguageName as missing (e.g., use
a falsy check like input.userLanguageName?.trim() ? input.userLanguageName :
input.textLanguageName) so translateCustomizationFields receives the
textLanguageName when userLanguageName is '' or whitespace; apply the same
change in the other branch that computes customizationLanguageName (both places
that call translateCustomizationFields) and, if userLanguageId should
independently resolve a name, ensure it is resolved to a non-empty language name
before this fallback logic.

Comment on lines +168 to +191
const { output } = await generateText({
model: google('gemini-2.5-flash'),
messages: [
{
role: 'system',
content: preSystemPrompt
},
{
role: 'user',
content: [
{
type: 'text',
text: prompt
}
]
}
],
output: Output.object({
schema: CustomizationFieldTranslationSchema
})
})

return output.translatedValue
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

AI SDK generateText Output.object undefined behavior

💡 Result:

The undefined behavior with generateText and Output.object in Vercel AI SDK (v6+) occurs when accessing result.output, which throws NoOutputGeneratedError despite valid JSON in result.text. This happens because the SDK only parses structured output if finishReason === "stop". Certain setups like AI Gateway or proxies return finishReason: undefined, causing the output getter to fail even with valid JSON present. Key details: - Issue tracked in GitHub #11348 (open as of 2026-03-06). - Affects AI SDK 6.0.0-beta.166 and similar versions. - Workaround: Use try/catch to fallback to manual parsing of result.text: import { generateText, Output } from 'ai'; import { z } from 'zod'; import { NoOutputGeneratedError } from 'ai'; const schema = z.object({ /* your schema / }); const result = await generateText({ model: / your model */, output: Output.object({ schema }), // ... }); let output; try { output = result.output; } catch (error) { if (NoOutputGeneratedError.isInstance(error) && result.text) { output = schema.parse(JSON.parse(result.text)); } else { throw error; } } - Official docs confirm result.output is a promise for structured output but don't detail this edge case. - Full gateway support for AI SDK 6 is in progress per Vercel team. Similar issues occur with tool calls (finishReason "tool-calls") or incompatible Zod schemas (e.g., .nullish with OpenAI).

Citations:


🏁 Script executed:

# Check AI SDK version in package.json
fd package.json --type f -x grep -l "ai" {} \; | head -5 | xargs grep -A 2 -B 2 '"ai"' | head -30

Repository: JesusFilm/core

Length of output: 208


🏁 Script executed:

# Check the full context of the function to understand error handling
cat -n apis/api-journeys-modern/src/schema/journeyAiTranslate/translateCustomizationFields/translateCustomizationFields.ts | head -200

Repository: JesusFilm/core

Length of output: 7766


🏁 Script executed:

# Look for similar error handling patterns in the codebase
rg "Output\.object" -A 5 -B 5 --type ts --type tsx

Repository: JesusFilm/core

Length of output: 85


Handle NoOutputGeneratedError from AI SDK's Output.object.

When using Output.object with generateText, the AI SDK throws NoOutputGeneratedError if the model response fails to parse against the schema (e.g., when finishReason !== "stop"). This can occur with proxies, AI Gateway, or incompatible responses. The current code doesn't handle this exception.

Replace the direct access with a try/catch block:

Suggested fix
- const { output } = await generateText({
+ let output;
+ try {
+   const result = await generateText({
      model: google('gemini-2.5-flash'),
      messages: [
        {
          role: 'system',
          content: preSystemPrompt
        },
        {
          role: 'user',
          content: [
            {
              type: 'text',
              text: prompt
            }
          ]
        }
      ],
      output: Output.object({
        schema: CustomizationFieldTranslationSchema
      })
    })
+   output = result.output;
+ } catch (error) {
+   if (error instanceof Error && error.message.includes('NoOutputGeneratedError')) {
+     throw new Error('Failed to parse translation response from AI model');
+   }
+   throw error;
+ }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apis/api-journeys-modern/src/schema/journeyAiTranslate/translateCustomizationFields/translateCustomizationFields.ts`
around lines 168 - 191, Wrap the generateText(...) call that uses
Output.object({ schema: CustomizationFieldTranslationSchema }) in a try/catch
that specifically catches the AI SDK's NoOutputGeneratedError (import it from
the SDK), so failures to parse the model response are handled; in the catch
block, surface a clear, contextual error (or return a defined fallback) instead
of letting the code access output.translatedValue when output is undefined, and
include any available raw model response or finishReason in the error/log to aid
debugging.

Comment on lines +361 to +387
if (translateWithAI && translateLanguage) {
const duplicatedJourneyId = await handleJourneyDuplication(
'signedIn',
journeyId
)

if (duplicatedJourneyId != null) {
handleNext(duplicatedJourneyId)
if (duplicatedJourneyId != null) {
const sourceLanguageName =
journey.language.name.find((name) => !name.primary)?.value ?? ''
const targetLanguageName =
translateLanguage.nativeName ?? translateLanguage.localName ?? ''

const userLanguageName =
values.languageSelect?.nativeName ??
values.languageSelect?.localName ??
''

setTranslationCompleted(false)
setTranslationVariables({
journeyId: duplicatedJourneyId,
name: journey.title,
journeyLanguageName: sourceLanguageName,
textLanguageId: translateLanguage.id,
textLanguageName: targetLanguageName,
userLanguageId: values.languageSelect?.id,
userLanguageName
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Build the AI prompt from the selected source template, not the current page journey.

journeyId can point at a different language variant via languagesJourneyMap, but this branch still sends journey.title and journey.language.name from the current journey. When the user starts from another template language, the translator gets the wrong source title/language and can overwrite the duplicated journey with a mistranslated copy.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/LanguageScreen/LanguageScreen.tsx`
around lines 361 - 387, The code builds the AI translation prompt using the
current page's journey data (journey.title and journey.language.name) even when
journeyId refers to a different language variant; instead, resolve the actual
source journey from languagesJourneyMap (or the map that links language variant
ids to their journey objects) using journeyId before calling
handleJourneyDuplication and when setting setTranslationVariables, and use that
sourceJourney.title and sourceJourney.language.name (falling back to existing
journey only if not found) for journeyLanguageName and name so the duplicated
journey is populated with the true source template title/language.

Comment on lines +494 to +528
<FormControlLabel
control={
<Checkbox
checked={values.translateWithAI}
onChange={async (e) => {
await setFieldValue(
'translateWithAI',
e.target.checked
)
if (!e.target.checked) {
await setFieldValue('translateLanguage', undefined)
}
}}
/>
}
label={t('Translate with AI')}
/>
{values.translateWithAI && (
<LanguageAutocomplete
value={values.translateLanguage}
languages={languagesData?.languages}
loading={languagesLoading}
onChange={(value) =>
setFieldValue('translateLanguage', value)
}
renderInput={(params) => (
<TextField
{...params}
placeholder={t('Search Language')}
label={t('Select Translation Language')}
variant="filled"
/>
)}
/>
)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t expose “Translate with AI” to guest quick-starts until the guest path supports it.

These controls render for guests, but the guest submit path still just duplicates the journey and ignores translateWithAI / translateLanguage. That silently drops the user’s selected target language.

🔧 Minimal safe fix
-                  <FormControlLabel
-                    control={
-                      <Checkbox
-                        checked={values.translateWithAI}
-                        onChange={async (e) => {
-                          await setFieldValue(
-                            'translateWithAI',
-                            e.target.checked
-                          )
-                          if (!e.target.checked) {
-                            await setFieldValue('translateLanguage', undefined)
-                          }
-                        }}
-                      />
-                    }
-                    label={t('Translate with AI')}
-                  />
-                  {values.translateWithAI && (
-                    <LanguageAutocomplete
-                      value={values.translateLanguage}
-                      languages={languagesData?.languages}
-                      loading={languagesLoading}
-                      onChange={(value) =>
-                        setFieldValue('translateLanguage', value)
-                      }
-                      renderInput={(params) => (
-                        <TextField
-                          {...params}
-                          placeholder={t('Search Language')}
-                          label={t('Select Translation Language')}
-                          variant="filled"
-                        />
-                      )}
-                    />
-                  )}
+                  {isSignedIn && (
+                    <>
+                      <FormControlLabel
+                        control={
+                          <Checkbox
+                            checked={values.translateWithAI}
+                            onChange={async (e) => {
+                              await setFieldValue(
+                                'translateWithAI',
+                                e.target.checked
+                              )
+                              if (!e.target.checked) {
+                                await setFieldValue(
+                                  'translateLanguage',
+                                  undefined
+                                )
+                              }
+                            }}
+                          />
+                        }
+                        label={t('Translate with AI')}
+                      />
+                      {values.translateWithAI && (
+                        <LanguageAutocomplete
+                          value={values.translateLanguage}
+                          languages={languagesData?.languages}
+                          loading={languagesLoading}
+                          onChange={(value) =>
+                            setFieldValue('translateLanguage', value)
+                          }
+                          renderInput={(params) => (
+                            <TextField
+                              {...params}
+                              placeholder={t('Search Language')}
+                              label={t('Select Translation Language')}
+                              variant="filled"
+                            />
+                          )}
+                        />
+                      )}
+                    </>
+                  )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/LanguageScreen/LanguageScreen.tsx`
around lines 494 - 528, The "Translate with AI" checkbox and
LanguageAutocomplete (controlled by values.translateWithAI and
values.translateLanguage and using setFieldValue) must not render for guest
quick-starts because the guest submit path ignores these fields; wrap the
existing FormControlLabel and the conditional LanguageAutocomplete in a guard
that detects the guest quick-start path (e.g., a prop/flag like
isGuestQuickStart or isGuest) and only render them when that flag is false so
guests cannot select translateWithAI/translateLanguage until the guest submit
flow supports it.

…lation of journey customization descriptions

- Introduced a new GraphQL mutation `journeyCustomizationDescriptionTranslate` to facilitate the translation of journey customization descriptions.
- Added input type `JourneyCustomizationDescriptionTranslateInput` to specify journey ID and language details.
- Implemented translation logic and integrated it with existing journey management features.
- Created corresponding tests to ensure functionality and handle edge cases, including permission checks and null/empty descriptions.
- Updated relevant components and hooks to utilize the new mutation for seamless integration in the UI.
- Updated various project scripts and GitHub workflows to use `tsx` instead of `ts-node` for improved performance and compatibility.
- Removed deprecated `ts-node` entries from package.json and pnpm-lock.yaml.
- Adjusted commands in workflow files to ensure consistent execution across different environments.
- Cleaned up unused `ButtonBlockUpdateInput` and related resolver code in the API, enhancing maintainability.
@github-actions github-actions bot requested a deployment to Preview - journeys-admin March 21, 2026 00:54 Pending
@github-actions github-actions bot temporarily deployed to Preview - short-links March 21, 2026 00:54 Inactive
@github-actions github-actions bot temporarily deployed to Preview - watch March 21, 2026 00:54 Inactive
@github-actions github-actions bot requested a deployment to Preview - resources March 21, 2026 00:54 Pending
@github-actions github-actions bot temporarily deployed to Preview - videos-admin March 21, 2026 00:54 Inactive
@github-actions github-actions bot temporarily deployed to Preview - journeys March 21, 2026 00:54 Inactive
@github-actions github-actions bot temporarily deployed to Preview - player March 21, 2026 00:54 Inactive
@github-actions github-actions bot temporarily deployed to Preview - watch-modern March 21, 2026 00:54 Inactive
@github-actions github-actions bot temporarily deployed to Preview - journeys March 21, 2026 00:59 Inactive
@github-actions github-actions bot temporarily deployed to Preview - player March 21, 2026 00:59 Inactive
@github-actions github-actions bot temporarily deployed to Preview - watch March 21, 2026 00:59 Inactive
@github-actions github-actions bot temporarily deployed to Preview - short-links March 21, 2026 00:59 Inactive
@github-actions github-actions bot temporarily deployed to Preview - resources March 21, 2026 00:59 Inactive
@github-actions github-actions bot temporarily deployed to Preview - videos-admin March 21, 2026 00:59 Inactive
@github-actions github-actions bot had a problem deploying to Preview - watch-modern March 21, 2026 00:59 Failure
@github-actions github-actions bot temporarily deployed to Preview - journeys-admin March 21, 2026 00:59 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants