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.
Log in to XTM Portal as an Administrator.
In the menu on the left of the screen, select the cog icon
. The Settings tab screen is displayed.
In the Settings tab screen, select the VALIDATOR tab. The VALIDATOR tab screen is displayed.
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.
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 rulesThe 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")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.fieldNamewhen you must keep the exact submitted string (for example, for comparisons against XML text nodes fromparam.getXml(...), not JSON).form.get("fieldName").implode(separator)– use this to decode a JSON array into an adapter (using theget("fieldName")method described above) and join all values in that bag using theimplode(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). Usevalues()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 offirstNameandlastName).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.
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 toform.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:
Security:
Store sensitive data (such as API keys, certificates) as secrets, not in the expression.
Use encrypted storage for the validator expression.
Performance:
Keep your expressions concise and efficient.
Avoid unnecessary external API calls.
Maintainability:
Add comments to explain complex validation rule logic.
Use descriptive variable and secret parameter names.
Test expressions thoroughly before deploying them.
Error-handling:
Provide clear, user-friendly error messages.
Handle API failures gracefully.
Common issues:
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.
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.
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:
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.
Log validation results:
Add logging to track validation flow.
Test incrementally:
Build complex expressions step by step. Test every new rule separately and in context with existing rules.
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:
Simple field validation
Example 16. : preventing users from submitting specific translation requestsUse 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)Making custom fields mandatory
Example 17. : setting a specific field as always mandatoryUse 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")Conditional field requirements
Example 18. 1: setting specific fields as mandatory for a specific source languageUse 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 typeUse 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 typesUse 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")Using user data in custom fields
Example 21. 1: populating a custom field with the logged-in user's nameUse 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 addressUse 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 formatUse 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 logicUse 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")File validation
Example 25. 1: blocking specific source file extensionsUse 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 providedUse 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 extensionsUse 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 duplicatesUse 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 extensionsUse 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 valuesUse the expression similar to the one below to reindex file extension values.
files.get("translationFiles").column("extension").values()Data modification
Example 31. : adding customer information to the descriptionUse 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)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 arraysUse 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:
External validation
Example 33. 1: authenticating a customer in the external serviceUse 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 validationUse 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) )File validation with customer-specific rules
Example 35. : setting customer-specific rules for file validationUse 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 )Testing Mode
Example 36. : blocking submission of test translation requestsUse 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 || */