Repository: novuhq/novu
File: packages/framework/src/client.ts (TypeScript)

Code before:
      const templateString = this.preprocessTranslationPatterns(JSON.stringify(templateControls));
      const parsedTemplate = this.templateEngine.parse(templateString);

      const renderVariables = {
        payload: event.payload,
        subscriber: event.subscriber,
        steps: buildSteps(event.state),
        t: {}, // Empty object so t.* properties are undefined and trigger default filters
      };

      const compiledString = await this.templateEngine.render(parsedTemplate, renderVariables);

      return JSON.parse(compiledString);
    } catch (error) {
      throw new StepControlCompilationFailedError(event.workflowId, event.stepId, error);
    }
  }

  /**
   * Preprocesses translation patterns to preserve them when values are undefined.
   * Transforms {{t.key}} to {{t.key | default: "{{t.key}}"}}
   */
  private preprocessTranslationPatterns(template: string): string {
    return template.replace(/\{\{\s*t\.([^}\s|]+)\s*\}\}/g, '{{ t.$1 | default: "{{t.$1}}" }}');
  }

  /**
   * Create the controls for a step, taking both the event controls and the default controls into account
   *
   * @param step The step to create the controls for
   * @param event The event that triggered the step
   * @returns The controls for the step
   */
  private async createStepControls(step: DiscoverStepOutput, event: Event): Promise<Record<string, unknown>> {
    const validatedControls = await this.validate(
      event.controls,
      step.controls.unknownSchema,
      'step',
      'controls',
      event.workflowId,
      step.stepId
    );

    return validatedControls;
  }

  private async previewStep(
    event: Event,
    step: DiscoverStepOutput
  ): Promise<Pick<ExecuteOutput, 'outputs' | 'providers'>> {
    try {

Review comment (suggestion):
I need to prevent Liquid engine in framework, which is triggered before API renderers, to replace `{{t.something}}` with empty string `""`. 

There is an option `strictVariables` or something like that which should leave the placeholders when variable doesnt exist in payload but I couldn't make it work for some reason.

This means that translations won't work for framework until upgrade to newest version.

Code after:
      const templateString = this.preprocessTranslationPatterns(JSON.stringify(templateControls));
      const parsedTemplate = this.templateEngine.parse(templateString);

      const renderVariables = {
        payload: event.payload,
        subscriber: event.subscriber,
        steps: buildSteps(event.state),
        t: {}, // Empty object so t.* properties are undefined and trigger default filters
      };

      const compiledString = await this.templateEngine.render(parsedTemplate, renderVariables);

      return JSON.parse(compiledString);
    } catch (error) {
      throw new StepControlCompilationFailedError(event.workflowId, event.stepId, error);
    }
  }

  /**
   * Preprocesses translation patterns to preserve them when values are undefined.
   * Transforms {{t.key}} to {{t.key | default: "{{t.key}}"}}
   */
  private preprocessTranslationPatterns(template: string): string {
    return template.replace(/\{\{\s*t\.(\w+(?:\.\w+)*)\s*\}\}/g, '{{ t.$1 | default: "{{t.$1}}" }}');
  }

  /**
   * Create the controls for a step, taking both the event controls and the default controls into account
   *
   * @param step The step to create the controls for
   * @param event The event that triggered the step
   * @returns The controls for the step
   */
  private async createStepControls(step: DiscoverStepOutput, event: Event): Promise<Record<string, unknown>> {
    const validatedControls = await this.validate(
      event.controls,
      step.controls.unknownSchema,
      'step',
      'controls',
      event.workflowId,
      step.stepId
    );

    return validatedControls;
  }

  private async previewStep(
    event: Event,
    step: DiscoverStepOutput
  ): Promise<Pick<ExecuteOutput, 'outputs' | 'providers'>> {
    try {