Skip to main content

XTM Portal 26.2

Setting up translation request validation

XTM Portal Administrators can configure different translation request validation and data processing rules. The validation is triggered before the translation request is deserialized and processed. For example, you could use it to specify that:

  • custom fields that are optional in XTM Cloud are mandatory in XTM Portal.

  • if one custom field has a specific value, another custom field is mandatory.

  • if a particular phrase is written in the Description field, in the Translation request, a specific text is displayed in the UI, and the form cannot be sent until some additional criteria are met.

Important

This section requires some prior knowledge of Symfony Expression Language. For details, see https://symfony.com/doc/current/reference/formats/expression_language.html.

If you need help with setting up validation rules for your XTM Portal Translation request form, request support:

To request support

Visit our Support Portal.

Procedure. To set up XTM Portal Translation request page validation:
  1. Log in to XTM Portal as an Administrator.

  2. In the menu on the left of the screen, select the cog icon gear_outline_brand_refreshed.svg. The Settings tab screen is displayed.

  3. In the Settings tab screen, select the VALIDATOR tab. The VALIDATOR tab screen is displayed.

  4. In the Validator expression section, set all required validation rules for your XTM Portal Translation request page. To do so, use Symfony Expression Language. You will also need XTM Portal field IDs to set your validation rules. You can check them in the HTML source code or the XTM Portal API. You can also request our XTM Support team for help.

    You can use several context variables:

    • form – contains data to be sent in the form. For details, see the Form data access section below.

    • files – offers convenient access to uploaded files grouped by field name. For details, see the Files data access section below.

    • user – contains the logged-in user's data. For details, see the User data access section below.

    • param – a helper that initially contains all configured secrets. For details, see the Parameter management section below.

    • httpClient – a service for executing network requests. For details, see the HTTPClient management section below.

    • error – use this variable to set validation errors (any entry here stops further processing). For details, see the Error-handling section below.

    You can use the following operators and logic:

    • && – logical AND.

    • || – logical OR.

    • ! – logical NOT.

    • == – equality comparison.

    • != – inequality comparison.

    • ~ – string concatenation.

    • ? – ternary operator.

    • ?? – null coalescing operator.

  5. If required, use the Secret parameters section to protect sensitive data (such as API keys, certificates, customer details). This data is then stored in encrypted form and only sent by the browser once, when it is created. It is never returned, unless it is used that way in a validator expression (for example, as an error message or field content).

    To create a new secret, select the Create button at the bottom of the Secret parameters section. Then, enter the required values in the Name and Value fields and select the Save button in the top right-hand corner. Saved secret parameters are listed in the Secret parameters section.

    To use the required secret in the validator, reference its name in the expression body. For details, see the example below.

    To delete a secret parameter, select the Delete button to the right of its name.

    Example 14. : using secret parameters in the Translation request form validation rules

    The Symfony expression below demonstrates how secret parameters (cert and passphrase) can be used in the validator expressions.

    httpClient
      .setLocalCert(param.cert)
      .setPassphrase(param.passphrase)
      .get("https://api.example.com/validation-endpoint?foo=" ~ form.customField_123)
      .getStatusCode() !== 200
    && error.set("customField_123", "Invalid value")

  6. Select the Save button in the top right-hand corner of the screen.

Result: the relevant Translation request page validation rules are saved.

Important

If you need assistance with creating or troubleshooting validator expressions, visit our Support Portal and create a support ticket.

Ensure that you include the following information in your support ticket:

  • Your current validator expression (if relevant).

  • Description of the desired behavior.

  • Any error messages that you are encountering (if relevant).

  • Steps to reproduce the issue (if relevant).

Available accessors:

  • form.fieldName – use this to access form field values. This property returns a raw string from the request bag. It does not support JSON decoding.

  • form.get("fieldName") – use this to read a field via the fluent adapter's method call.

    If the stored value is a string containing valid JSON, it is decoded to PHP types (boolean, arrays, objects). Decoded arrays (and JSON objects) are wrapped in the same fluent adapter so you can chain helpers such as .column(), .diff(), and .implode(). Invalid JSON or non-JSON strings are left unchanged.

    Use property access form.fieldName when you must keep the exact submitted string (for example, for comparisons against XML text nodes from param.getXml(...), not JSON).

  • form.get("fieldName").implode(separator) – use this to decode a JSON array into an adapter (using the get("fieldName") method described above) and join all values in that bag using the implode(separator) accessor) with an optional separator (default ", ").

    Typical pattern: use this for multi-value fields submitted as JSON arrays. Decode with get(), then join for a single-line custom field using .implode().

    For details, see the Multi-value fields: JSON decode and implode() example.

  • form.getDigits("fieldName") – use this to extract digits from a field value.

  • form.set("fieldName", value) – use this to set a field value.

  • form.fieldName matches "pattern" – use this for pattern matching.

  • form.fieldName ends with "suffix" – use this for string suffix checking.

  • form.fieldName contains "substring" – use this for substring checking.

The files variable provides convenient access to the uploaded files, grouped by a field name. Each file entry has the name and extension (lowercased) properties.

Available accessors:

  • files.fieldName – use this to access files for a specific field.

  • files.get("fieldName") – use this to get files for a specific field.

  • files.fieldName.column("key") – use this to extract a column from file entries (e.g., "extension", "name").

  • files.fieldName.column("extension").diff([...]) – use this to compare file extensions against the allowed list.

  • files.fieldName.column("extension").unique() – use this to get unique file extensions.

  • files.fieldName.column("extension").values() – use this to reindex extension values.

  • files.fieldName.column("extension").count() – use this to count file entries.

Note

The files variable returns an adapter that supports array helper methods. Only selected helpers are supported and behave like PHP array_* functions by returning, when applicable, an adapter for chaining when the result is an array.

Limitations:

  • filter(...) uses the original ParameterBag method (not array_filter). Use the form-specific behavior (e.g., email validation helper), not a generic callback filter.

  • keys() uses native bag key access (not array_keys). Use values() when you need reindexing, for example, array_values.

  • map(...) is not supported. Do not use it.

The user parameter contains the logged-in user's data. It is populated with the authenticated user's information automatically, using the validator serializer group.

Available accessors:

  • user.id – user's unique identifier.

  • user.email – user's email address.

  • user.firstName – user's first name.

  • user.lastName – user's last name.

  • user.fullName – user's full name (concatenation of firstName and lastName).

  • user.nameAddrEmail – user's name and email in mailbox format (e.g., "John Doe"<john@example.com>").

  • user.isAdmin – a boolean indicating if the user has Administrator privileges.

Example 15. : user data structure
array:7 [
  "id" => 664797
  "email" => "portaladmin@xtm-intl.com"
  "firstName" => "Test"
  "lastName" => "Admin"
  "isAdmin" => true
  "fullName" => "Test Admin"
  "nameAddrEmail" => "Test Admin <portaladmin@xtm-intl.com>"
]

Available accessors:

  • param.set("key", value) – use this to set a parameter value.

  • param.key – use this to access parameter values. This accessor returns raw string from the request bag. It does not support JSON decoding.

  • param.get("key") – use this when the value is a JSON string. Similarly to form.get("fieldName"), useful if a parameter was set from a stringified payload:

    If the stored value is a string containing valid JSON, it is decoded to PHP types (boolean, arrays, objects). Decoded arrays (and JSON objects) are wrapped in the same fluent adapter so you can chain helpers such as .column(), .diff(), and .implode(). Invalid JSON or non-JSON strings are left unchanged.

  • param.getXml("key") – use this to parse XML content from a parameter.

Array Helpers on Parameter Bags:

Note

Only selected helpers are supported and behave like PHP array_* functions by returning, when applicable, an adapter for chaining when the result is an array. They are listed below.

  • param.column("key") – use this to extract a column (equivalent to array_column).

  • param.diff([...]) – use this to compute a difference (equivalent to array_diff).

  • param.values() – use this to reindex values (equivalent to array_values).

  • param.unique() – use this to remove duplicates (equivalent to array_unique).

These helpers can be chained together. For example:

param.column("extension").diff(["pdf"]).count().

Available accessors:

  • httpClient.setHeaders({...}) – use this to set the request headers.

  • httpClient.setLocalCert("certificate") – use this to set the SSL certificate.

  • httpClient.setPassphrase("passphrase") – use this to set certificate passphrase.

  • httpClient.verifyPeer(false) – use this to disable SSL peer verification.

  • httpClient.request("METHOD", "URL") – use this to make an HTTP request.

  • httpClient.post("URL") – use this to make a POST request.

  • httpClient.getContent(false) – use this to get the response content.

  • httpClient.getStatusCode() – use this to get the response status code.

Available accessors:

  • error.set("fieldName", "error message") – use this to set a validation error for a field.

  • error.fieldName – use this to access the error container.

Note

Form, param, and nested adapters implement Stringable. Error messages must be string or Stringable. Otherwise, a generic message is used.

For the best results, ensure that you follow these rules while setting up your translation request validation rules:

  1. Security:

    • Store sensitive data (such as API keys, certificates) as secrets, not in the expression.

    • Use encrypted storage for the validator expression.

  2. Performance:

    • Keep your expressions concise and efficient.

    • Avoid unnecessary external API calls.

  3. Maintainability:

    • Add comments to explain complex validation rule logic.

    • Use descriptive variable and secret parameter names.

    • Test expressions thoroughly before deploying them.

  4. Error-handling:

    • Provide clear, user-friendly error messages.

    • Handle API failures gracefully.

Common issues:

  1. Expression syntax errors

    Measures to take:

    • Ensure that all operators have been used properly.

    • Ensure that all parentheses are balanced, which means every opening bracket has a corresponding, correctly ordered closing bracket.

    • Ensure that all string literals have been quoted properly.

  2. Field access issues

    Measures to take:

    • Ensure that all field names match exactly.

    • Ensure that all custom field IDs are correct.

    • Ensure that a field exists in the form data.

  3. External API issues

    Measures to take:

    • Ensure that API endpoints are accessible.

    • Ensure that authentication credentials are correct.

    • Ensure that the SSL certificate has been configured properly.

Debugging tips:

  1. Use testing mode:

    Implement a testing mode that can be enabled/disabled easily. Introduce new validation rules in testing mode first, before you use them in your production system.

  2. Log validation results:

    Add logging to track validation flow.

  3. Test incrementally:

    Build complex expressions step by step. Test every new rule separately and in context with existing rules.

  4. Validate syntax:

    Test expressions in a development environment first, before you use them in production.

Important

If you need assistance with creating or troubleshooting validator expressions, visit our Support Portal and create a support ticket.

Ensure that you include the following information in your support ticket:

  • Your current validator expression (if relevant).

  • Description of the desired behavior.

  • Any error messages that you are encountering (if relevant).

  • Steps to reproduce the issue (if relevant).

Basic examples:

  1. Simple field validation

    Example 16. : preventing users from submitting specific translation requests

    Use the expression below to prevent users from submitting translation requests with the Polish source language for a specific customer.

    form.sourceLanguage ends with "pl_PL" && form.customer == "/customers/1" &&
    param.set("msg", "Customer and sourceLanguage combination is not allowed") &&
    error.set("customer", param.msg).set("sourceLanguage", param.msg)

  2. Making custom fields mandatory

    Example 17. : setting a specific field as always mandatory

    Use the expression below to check if a custom field with ID 123 has been filled, in the translation request form. If the field is empty, an error message, "This field is required", is displayed next to the relevant custom field.

    !form.customField_8106 && error.set("customField_123", "This field is required")

  3. Conditional field requirements

    Example 18. 1: setting specific fields as mandatory for a specific source language

    Use the expression below to make the custom field with ID 456 mandatory when the source language is German. If the field is empty, an error message, "This field is required for German translations", is displayed.

    form.sourceLanguage ends with "de_DE" && !form.customField_456 &&
    error.set("customField_456", "This field is required for German translations")

    Example 19. 2: setting specific fields as mandatory for a specific customer type

    Use the expression below to make the custom fields with IDs  456 and 789 mandatory for premium customers (IDs 1, 2, and 3). If one of these fields is empty, an error message, "Premium customers must complete both fields", is displayed.

    form.getDigits('customer') in [1, 2, 3] && 
    (!form.customField_456 || !form.customField_789) &&
    param.set("msg", "Premium customers must complete both fields") &&
    error.set("customField_456", param.msg).set("customField_789", param.msg)

    Example 20. 3: setting specific fields as mandatory for a specific project types

    Use the expression similar to the one below to require a "Cost center" custom field (whose ID is 1234) only when the project name contains the "financial" keyword. If the field is empty, an error message, "Cost center is required for financial projects", is displayed.

    /* Cost Center CF ID: 1234 */
    form.name contains "financial" && 
    !form.customField_1234 &&error.set("customField_1234", "Cost center is required for financial projects")

  4. Using user data in custom fields

    Example 21. 1: populating a custom field with the logged-in user's name

    Use the expression similar to the one below to automatically populate a custom field with ID 1234 with the logged-in user's full name.

    form.set("customField_1234", user.fullName)

    Example 22. 2: populating a custom field with the logged-in user's email address

    Use the expression similar to the one below to automatically populate a custom field with ID 1234 with the logged-in user's email address.

    form.set("customField_1234", user.email)

    Example 23. 3: populating a custom field with the logged-in user's name and email address in mailbox format

    Use the expression similar to the one below to automatically populate a custom field with ID 1234 with the logged-in user's name and email in mailbox format.

    form.set("customField_1234", user.nameAddrEmail)

    Example 24. 4: using the logged-in user's data in a conditional logic

    Use the expression similar to the one below to verify if the logged-in user is an XTM Portal Administrator and to prevent them from creating a translation request for a premium customer (whose ID is 1) if they are not.

    user.isAdmin && form.customer == "/customers/1" ||
    !user.isAdmin && form.customer == "/customers/1" &&
    error.set("customer", "Only administrators can create requests for premium customers")

  5. File validation

    Example 25. 1: blocking specific source file extensions

    Use the expression similar to the one below to permit only selected source file formats if a project template with ID 1234 has been selected. If a user uploads an unsupported file format, an error message, "Unsupported source file format. Upload a Word or Excel file (DOC, DOCX, XLSX) and try again.", is displayed.

    form.template = '/templates/1234' &&
    files.get("translationFiles").column("extension").diff(["doc","docx","xlsx"]).count() !== 0 &&
    error.set("translationFiles", "Unsupported source file format. Upload a Word or Excel file (DOC, DOCX, XLSX) and try again.")

    Example 26. 2: checking if reference files are provided

    Use the expression similar to the one below to check if a user has uploaded reference materials for their translation request. If not, an error message, "You must upload a reference file for translations", is displayed.

    !files.referenceFiles && error.set("referenceFiles", "You must upload a reference file for translations.")

    Example 27. 3: blocking specific translation file extensions

    Use the expression similar to the one below to validate reference file extensions. If a user uploads an unsupported file format, an error message, "Unsupported reference file format. Upload a PDF, PNG, JPEG, or a Word (DOC or DOCX) file with screenshots.", is displayed.

    files.get("referenceFiles").column("extension").diff(["pdf","png","jpeg","jpg","doc","docx"]).count() !== 0 &&
    error.set("referenceFiles", "Unsupported reference file format. Upload a PDF, PNG, JPEG, or a Word (DOC or DOCX) file with screenshots.")

    Example 28. 4: extracting file extensions and removing duplicates

    Use the expression similar to the one below to extract a column, remove duplicates, and count files.

    files.get("translationFiles").column("extension").unique().count()

    Example 29. 5: counting unsupported file extensions

    Use the expression similar to the one below to count unsupported file extensions.

    files.get("referenceFiles").column("extension").diff(["pdf","png","jpeg","jpg","doc","docx"]).count()

    Example 30. 6: reindexing file extension values

    Use the expression similar to the one below to reindex file extension values.

    files.get("translationFiles").column("extension").values()

  6. Data modification

    Example 31. : adding customer information to the description

    Use the expression similar to the one below to add customer information to the description automatically.

    param
     .set("id", form.getDigits("customer"))
     .set(
      "info",
      param.id ?
       httpClient
        .setPassphrase(param.pass)
        .setLocalCert(param.cert)
        .verifyPeer(false)
        .request("GET", "https://check_customer/" ~ param.id)
        .toArray()["info"] ?? ''
     ) &&
    param.info &&
    form.set("description", form.description ~ "
    Customer #" ~ param.id ~ ": " ~ param.info)

  7. Multi-value fields: JSON decode and implode()

    Some fields (e.g. the Share with field where users can enter multiple email addresses) arrive as JSON-encoded arrays in the request. You can use form.get("fieldName") so the value is decoded, then .implode() to produce a single comma-separated string for a text custom field.

    Use an explicit separator if needed: form.get("shareWith").implode("; ").

    Example 32. : decoding JSON encoded arrays

    Use the expression similar to the one below to decode JSON array and write comma-separated values to a text custom field.

    form.set("customField_123", form.get("shareWith").implode())

Advanced examples:

  1. External validation

    Example 33. 1: authenticating a customer in the external service

    Use the expression below to authenticate a customer in the external service and add their information to the description.

    param
     .set("id", form.getDigits("customer"))
     .set("info", param.id ?
       httpClient
        .verifyPeer(false)
        .get("https://customers.example/" ~ param.id)
        .toArray(false)["info"] ?? ''
     ) && (
      param.info && form.set("description", form.description ~ " Customer #" ~ param.id ~ ": " ~ param.info)
      || error.set("customer", "Customer not found.")
    )

    Example 34. 2: external SOAP API validation

    Use the expression similar to the one below to validate a company code custom field in an external SOAP API.

    form.customField_123 &&
    httpClient
      .setHeaders({
        client_id: param.client_id,
        client_secret: param.client_secret,
        SoapAction: 'checkCompany',
        'Content-Type': 'text/xml; charset=utf-8',
      })
      .setLocalCert(param.cert)
      .setPassphrase(param.passphrase)
      .setBody("
    <?xml version='1.0' encoding='UTF-8'?>
    <S:Envelope xmlns:S='http://schemas.xmlsoap.org/soap/envelope/'>
      <S:Body>
        <checkCompanyRequest xmlns='http://example.com/soap/company/v1'>
          <companyCode>" ~ form.customField_123 ~ "</companyCode>
        </checkCompanyRequest>
      </S:Body>
    </S:Envelope>
    ")
    && param
      .set("res", httpClient.post("https://integration.example.org/soap/service"))
      .set("content", param.res.getContent(false))
      .set("xml", param.res.getStatusCode() == 200 ? param.getXml("content") : null)
    && param.xml?.isValid != "true"
    && error.set("customField_123",
         "VALIDATION ERROR: " ~ (param.xml?.validationMessage ?? param.content)
    )

  2. File validation with customer-specific rules

    Example 35. : setting customer-specific rules for file validation

    Use the expression similar to the one below to validate supported file types and an email field for a specific customer with ID 1234.

    If a user uploads an unsupported source file format, an error message, "Unsupported reference file format. Upload a Word or Excel file (DOC, DOCX, XLSX) and try again." , is displayed. If a user uploads an unsupported reference file format, an error message, "Unsupported file format. Upload a PDF, PNG, JPEG, or a Word file with screenshots.", is displayed. If a user does not provide an email address, an error message, "Valid email required.", is displayed.

    form.customer === "/customers/1234" && (
      files.get("translationFiles").column("extension").diff(["doc","docx","xlsx"]).count() !== 0
        && error.set("translationFiles", "Unsupported file format. Upload a Word or Excel file (DOC, DOCX, XLSX) and try again.") && false
      || !files.referenceFiles && error.set("referenceFiles", "Required.") && false
      || files.referenceFiles && files.get("referenceFiles").column("extension").diff(["pdf","png","jpeg","jpg","doc","docx"]).count() !== 0
        && error.set("referenceFiles", "Unsupported file format. Upload a PDF, PNG, JPEG, or a Word file with screenshots.") && false
      || !form.filter("customField_123", "", 274) && error.set("customField_123", "Valid email required.") && false
    )

  3. Testing Mode

    Example 36. : blocking submission of test translation requests

    Use the expression similar to the one below to use the validator for tests/debugging. The "TEST"phrase in the form name blocks the form from being sent.

    /* TESTING: uncomment line below to use the validator when the name field contains "TEST" */
    /*not (form.name matches "/TEST/") || error.set("name", "TEST: this is a fake error to block the form from being sent.") && false || */