feat: translate quick start#8875
Conversation
…tps://github.com/JesusFilm/core into 25-00-NC-feat-ai-translate-quick-start-templates
- 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.
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds AI-powered translation for journey customization descriptions and fields, new translation helpers/tests, a dedicated description-translation mutation, integration into existing mutation/subscription flows (with user-language inputs), UI wiring, cache updates, schema changes, seed/template updates, and related test adjustments. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant UI as Admin UI
participant Sub as Translate Subscription
participant API as Journeys API
participant AI as Google Gemini
participant DB as Database
User->>UI: Initiate duplicate + translate
UI->>Sub: Start subscription (journeyId, textLanguage, userLanguage?)
Sub->>API: Subscription event / progress
API->>DB: Fetch journey, blocks, journeyCustomizationFields
API->>AI: Stream block translations (per-card)
AI-->>API: Block translation chunks
API-->>Sub: Stream progress updates
API->>AI: Batch translate customization description & field values
AI-->>API: Translated description & field values
API->>DB: Update blocks, journeyCustomizationFields, journeyCustomizationDescription
API-->>Sub: Final translated journey (100%)
Sub-->>UI: Progress 100% + translated journey
UI->>User: Navigate / show success
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
💰 Infracost reportMonthly estimate increased by $155 📈
*Usage costs can be estimated by updating Infracost Cloud settings, see docs for other options. Estimate details (includes details of unsupported resources) |
|
Ran Plan for dir: Show OutputTerraform 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)
╵
Plan: 2 to add, 5 to change, 4 to destroy.
|
… 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.
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run journeys-e2e:e2e |
❌ Failed | 39s | View ↗ |
nx run resources-e2e:e2e |
✅ Succeeded | 1m 13s | View ↗ |
nx run videos-admin-e2e:e2e |
✅ Succeeded | 4s | View ↗ |
nx run journeys-admin-e2e:e2e |
✅ Succeeded | 30s | View ↗ |
nx run watch-e2e:e2e |
✅ Succeeded | 25s | View ↗ |
nx run player-e2e:e2e |
✅ Succeeded | 3s | View ↗ |
nx run watch-modern-e2e:e2e |
✅ Succeeded | 3s | View ↗ |
nx run short-links-e2e:e2e |
✅ Succeeded | 3s | View ↗ |
Additional runs (24) |
✅ Succeeded | ... | View ↗ |
☁️ Nx Cloud last updated this comment at 2026-04-21 23:52:05 UTC
|
View your CI Pipeline Execution ↗ for commit c052613
☁️ Nx Cloud last updated this comment at |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
libs/journeys/ui/src/libs/resolveJourneyCustomizationString/resolveJourneyCustomizationString.spec.ts (1)
78-84: Rename this test to match current assertions.Line 78 says “key-only,” but Line 83 asserts an inline-value token (
{{ title: ... }}). Renaming will make intent clearer.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@libs/journeys/ui/src/libs/resolveJourneyCustomizationString/resolveJourneyCustomizationString.spec.ts` around lines 78 - 84, The test name for the it(...) block no longer matches the assertions — it says "key-only" but asserts both a key-only token and an inline-value token; update the test description in the resolveJourneyCustomizationString.spec.ts it(...) declaration to reflect both cases (e.g., "replaces key-only and inline-value custom fields regardless of surrounding whitespace") so the intent matches the assertions for resolveJourneyCustomizationString.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@libs/journeys/ui/src/libs/resolveJourneyCustomizationString/resolveJourneyCustomizationString.spec.ts`:
- Around line 78-84: The test name for the it(...) block no longer matches the
assertions — it says "key-only" but asserts both a key-only token and an
inline-value token; update the test description in the
resolveJourneyCustomizationString.spec.ts it(...) declaration to reflect both
cases (e.g., "replaces key-only and inline-value custom fields regardless of
surrounding whitespace") so the intent matches the assertions for
resolveJourneyCustomizationString.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 33c171a8-fe75-4fd3-887a-435b9a1a78fd
📒 Files selected for processing (1)
libs/journeys/ui/src/libs/resolveJourneyCustomizationString/resolveJourneyCustomizationString.spec.ts
…scriptionTargetLanguageName - Added descriptionTargetLanguageName parameter to support translation of customization descriptions. - Updated related tests to reflect changes in description translation logic. - Refactored existing code to improve clarity and maintainability, ensuring fallback to targetLanguageName when descriptionTargetLanguageName is not provided.
|
Merge conflict attempting to merge this into stage. Please fix manually. |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyCustomizationDescriptionTranslate.mutation.spec.ts (1)
133-171: One of these skip tests is redundant.Lines 133-151 and Lines 153-171 both set
journeyCustomizationDescription: nulland assert the same no-op path. Please merge them, or change one case to cover''explicitly if you want separate empty-string coverage.🤖 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/journeyCustomizationDescriptionTranslate.mutation.spec.ts` around lines 133 - 171, Two tests in journeyCustomizationDescriptionTranslate.mutation.spec.ts are redundant because both set journeyCustomizationDescription: null and assert the same no-op behavior; either merge them into one test or change the second ("should skip all translation when no description") to explicitly cover an empty-string case by setting journeyCustomizationDescription: '' (and update the test title accordingly) so that mockTranslateDescription and prismaMock.journey.update expectations still assert no calls; adjust the mocked returns for prismaMock.journey.findUnique and findUniqueOrThrow and the variable setup (mockInput) to match the chosen case.
🤖 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/translateCustomizationFields/translateCustomizationFields.ts`:
- Around line 143-186: The current filter logic in translateCustomizationFields
builds valueEntries, defaultValueEntries and will call
translateCustomizationDescription even for empty or whitespace-only strings;
update the checks so you skip blank text by trimming and verifying non-empty
content: when creating fieldsWithContent/valueEntries/defaultValueEntries only
include f.value and f.defaultValue when typeof === 'string' and
f.value.trim().length > 0 (same for defaultValue), and change the
journeyCustomizationDescription branch to check description.trim().length > 0
before calling translateCustomizationDescription; keep the same
translateBatch/translateCustomizationDescription calls and just ensure inputs
are non-blank trimmed strings so empty or whitespace-only fields are not sent to
Gemini.
- Around line 224-260: The code currently extracts placeholder tokens using
fieldPattern/fieldMatches and embeds them into the prompt, but never verifies
the model output (output.translatedDescription) still contains the exact {{ ...
}} blocks; add a post-translation validation step that re-parses
translatedDescription with the same fieldPattern, compare the resulting
set/order/strings to the original fieldMatches (from fieldMatches variable), and
if any token is missing/changed (spacing/quoting/content), either retry the
generation with stricter instructions or throw/log an error and fall back to a
safe behavior (e.g., return the original description or restore original tokens
in-place). Perform this check right after generateText and before returning
output.translatedDescription, referencing generateText,
output.translatedDescription, fieldPattern, fieldMatches, and
CustomizationDescriptionTranslationSchema to locate where to insert the
validation and error-handling.
---
Nitpick comments:
In
`@apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyCustomizationDescriptionTranslate.mutation.spec.ts`:
- Around line 133-171: Two tests in
journeyCustomizationDescriptionTranslate.mutation.spec.ts are redundant because
both set journeyCustomizationDescription: null and assert the same no-op
behavior; either merge them into one test or change the second ("should skip all
translation when no description") to explicitly cover an empty-string case by
setting journeyCustomizationDescription: '' (and update the test title
accordingly) so that mockTranslateDescription and prismaMock.journey.update
expectations still assert no calls; adjust the mocked returns for
prismaMock.journey.findUnique and findUniqueOrThrow and the variable setup
(mockInput) to match the chosen case.
🪄 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: 023f8b27-6d5e-470d-ae01-7610e134fbdd
📒 Files selected for processing (6)
apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.spec.tsapis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.tsapis/api-journeys-modern/src/schema/journeyAiTranslate/journeyCustomizationDescriptionTranslate.mutation.spec.tsapis/api-journeys-modern/src/schema/journeyAiTranslate/journeyCustomizationDescriptionTranslate.mutation.tsapis/api-journeys-modern/src/schema/journeyAiTranslate/translateCustomizationFields/translateCustomizationFields.spec.tsapis/api-journeys-modern/src/schema/journeyAiTranslate/translateCustomizationFields/translateCustomizationFields.ts
✅ Files skipped from review due to trivial changes (1)
- apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.spec.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyCustomizationDescriptionTranslate.mutation.ts
- apis/api-journeys-modern/src/schema/journeyAiTranslate/translateCustomizationFields/translateCustomizationFields.spec.ts
- apis/api-journeys-modern/src/schema/journeyAiTranslate/journeyAiTranslate.ts
|
Merge conflict attempting to merge this into stage. Please fix manually. |
|
Merge conflict attempting to merge this into stage. Please fix manually. |
|
Merge conflict attempting to merge this into stage. Please fix manually. |
- Introduced a utility function to check for non-blank values, enhancing the filtering of fields with content. - Updated logic to ensure that customization descriptions are properly validated before returning null values. - Added verification for preserved tokens in translated descriptions to prevent mangling during translation.
…zation string - Modified test cases to reflect the correct expected values for the default variant in end-user rendering. - Updated function documentation to clarify the resolution logic for label strings, ensuring it prioritizes 'value' over 'defaultValue' for non-admin variants.
|
Merge conflict attempting to merge this into stage. Please fix manually. |
- Corrected the language IDs for Spanish and Korean in the middleware configuration. - Updated corresponding test cases to reflect the new IDs, ensuring consistency across the application.
…te-quick-start Made-with: Cursor # Conflicts: # apps/journeys-admin/middleware.ts # apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/LanguageScreen/LanguageScreen.spec.tsx
|
Merge conflict attempting to merge this into stage. Please fix manually. |
|
Merge conflict attempting to merge this into stage. Please fix manually. |
|
Found 1 test failure on Blacksmith runners: Failure
|
…nputs - Introduced `blockDuplicate` mutation to allow duplication of blocks with optional parent order and ID mapping. - Removed unused `journeyCustomizationDescriptionTranslate` mutation and related input types from the GraphQL schema. - Updated `JourneyAiTranslateInput` to simplify translation inputs by removing unnecessary fields. - Refactored quick start template customization description for clarity and conciseness. - Enhanced tests for block duplication and journey translation functionalities.
…nputs - Introduced `blockDuplicate` mutation to allow duplication of blocks with optional parent order and ID mapping. - Removed unused `journeyCustomizationDescriptionTranslate` mutation and related input types from the GraphQL schema. - Updated `JourneyAiTranslateInput` to simplify translation inputs by removing unnecessary fields. - Refactored quick start template customization description for clarity and conciseness. - Enhanced tests for block duplication and journey translation functionalities.
…-MA-feat-translate-quick-start
…y accidental commit Restores the expanded multi-step quickStartTemplate with customization fields (church_name, feedback_label, website_label, email_label) and the JourneyCustomizationDescriptionTranslateInput + userLanguageId/ userLanguageName fields in the generated graphql types. Made-with: Cursor

Summary by CodeRabbit
New Features
Behavior Changes
Tests
Documentation