Repository navigation
feat: localize validation failures and append per-entry codes - #697
Conversation
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Reviewed the complete validation-localization change and checked the existing web/mobile error-response consumers.
- Localized validation responses: The exception handler uses the existing request-language resolver, preserving stored-account language precedence over
Accept-Language. - Additive error details: Ordered legacy
errorsstring arrays remain intact, with matching per-entry codes and messages appended inerrorDetails. - Shared validation copy: Custom validator messages move into the bilingual catalog; built-in failures retain validator-name codes, and counted/dynamic placeholders are preserved.
- Verification: All 32 targeted Infrastructure tests and 1,093 Application catalog/copy tests passed locally. The full suite was not rerun for this review.
gpt-6.1-sol | 𝕏
The NullValidator fallback said "deve estar null" in a Portuguese sentence; it now says "deve estar vazio", which matches its English text. The friend request identifier message now says "exatamente um", which matches "exactly one" in English. Refs thomasluizon/orbit-tickets#606 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Copy approval for the new en and pt-BR validation strings ( Verdict: AGREE, confidence medium. Two defects were named:
Both are fixed in |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Reviewed c05ef71d against the previous review at ca416a91, with the full PR diff as context.
- Corrected identifier guidance: Changed the Portuguese friend-request message to require exactly one identifier, matching the existing validation rule.
- Corrected empty-value copy: Replaced the literal
nullin the PortugueseNullValidatormessage with natural Portuguese wording, without changing validation behavior or error codes. - Verified compatibility and tests: Checked the sibling shared error consumer and ran the Application catalog/copy and friend-request validator tests; all 1,102 passed. The full suite was not rerun.
gpt-6.1-sol | 𝕏
|




A five-digit verification code now returns localized validation copy and a stable code for each failure. The existing
errorsdictionary remains a dictionary of ordered string arrays; an appendederrorDetailsdictionary contains matching{ code, message }entries.Refs thomasluizon/orbit-tickets#606
Intended approach
WithMessagesites undersrc/withWithCopy, including shared title and bulk-operation rules.src/Orbit.Application/Common/ValidationErrorCodes.csnames the custom codes, andErrorCopy.Validation.cssupplies English and pt-BR copy for custom and built-in failures.ValidationCopyExtensions.csretains counted and dynamic arguments in FluentValidation state and fills its message placeholders after calling the existingErrorCopy.TryResolve.src/Orbit.Api/Middleware/ValidationExceptionHandler.csobtains the existing scopedIRequestLanguageResolverfrom the request services. Stored account language therefore wins overAccept-Language, using the same resolver asLocalizedErrorResultFilter.ValidationLocalizationTests,ValidationExceptionHandlerTests,ValidationCopyTestsandErrorCopyTestsverifies all 22 validator families, request-language precedence, message/code alignment, placeholders and catalog completeness. No domain rules or DTO fields were removed or retyped.Assumptions
errorDetailsbesideerrors; rejected changing the existing string arrays to objects, which would break installed clients. Field and array order are preserved.Test evidence
dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~ValidationExceptionHandlerTests --verbosity quietpassed both unchanged tests while the defect was present.PortugueseRequest_LocalizesTheRealFiveDigitCodeFailure, using the realVerifyCodeCommandValidator. Before implementation,dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~PortugueseRequest_LocalizesTheRealFiveDigitCodeFailure --logger 'console;verbosity=normal'failed: actualCode must be a 6-digit number, expectedO código deve ter 6 dígitos.message = e.LocalizedMessage(isPtBr)withmessage = e.ErrorMessagein the handler.dotnet test tests/Orbit.Infrastructure.Tests --filter 'FullyQualifiedName~EveryValidatorFamily_ReturnsPortugueseCopyAndAlignedCodes|FullyQualifiedName~PortugueseRequest_LocalizesTheRealFiveDigitCodeFailure' --logger 'console;verbosity=normal'failed all 23 cases. Restored the resolver and randotnet test tests/Orbit.Infrastructure.Tests --filter 'FullyQualifiedName~ValidationLocalizationTests|FullyQualifiedName~ValidationExceptionHandlerTests' --verbosity quiet: all 32 cases passed. The bypass was never committed.dotnet test tests/Orbit.Application.Tests --filter 'FullyQualifiedName~ValidationCopyTests|FullyQualifiedName~ErrorCopyTests' --logger 'console;verbosity=normal': all 1,093 cases passed.env -u LANG dotnet build Orbit.slnxandenv -u LANG dotnet test: zero build errors, all 9,041 tests passed.env LC_ALL=en_US.UTF-8 dotnet build Orbit.slnxandenv LC_ALL=en_US.UTF-8 dotnet test: zero build errors, all 9,041 tests passed.rg -n 'WithMessage\("' src --glob '*.cs'found no literal sites. No added bare narration comments. Pre-commit formatting, dash, timeless, root and suppression checks passed.External interface evidence
FluentValidation 12.1.1 was invoked directly rather than modeled with fabricated failures. The family test logs serialize the complete failures produced by the real validators. Reproduce the verification-code and audio examples below with
dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~EveryValidatorFamily_ReturnsPortugueseCopyAndAlignedCodes --logger 'console;verbosity=detailed'.The observed fields consumed here are
ErrorCode,ErrorMessage,PropertyName,CustomStateandFormattedMessagePlaceholderValues. Dynamic and counted state is our ownValidationCopyArgumentstype. Built-inLength(6)emitsExactLengthValidatorand carriesMaxLengthandTotalLength; these names were read from a real failure. A direct installed-package invocation also confirmed descriptor components as(IPropertyValidator, IRuleComponent)tuples, theirName/ErrorCodevalues, andChildValidatorAdaptoras a composition node; the guard does not treat that node as a failure.[ { "PropertyName": "Code", "ErrorMessage": "'Code' must be 6 characters in length. You entered 5 characters.", "AttemptedValue": "12345", "CustomState": null, "Severity": 0, "ErrorCode": "ExactLengthValidator", "FormattedMessagePlaceholderValues": { "MinLength": 6, "MaxLength": 6, "TotalLength": 5, "PropertyName": "Code", "PropertyValue": "12345", "PropertyPath": "Code" } }, { "PropertyName": "Code", "ErrorMessage": "Code must be a 6-digit number", "AttemptedValue": "12345", "CustomState": { "Values": [] }, "Severity": 0, "ErrorCode": "VALIDATION_VERIFICATION_CODE_FORMAT", "FormattedMessagePlaceholderValues": { "RegularExpression": "^\\d{6}$", "PropertyName": "Code", "PropertyValue": "12345", "PropertyPath": "Code" } }, { "PropertyName": "FileName", "ErrorMessage": "Audio format '.xyz' is not supported.", "AttemptedValue": "voice.xyz", "CustomState": { "Values": [ ".xyz" ] }, "Severity": 0, "ErrorCode": "VALIDATION_AUDIO_FORMAT", "FormattedMessagePlaceholderValues": { "PropertyName": "File Name", "PropertyValue": "voice.xyz", "PropertyPath": "FileName" } } ]Manual steps
main, Thomas uses GitHub Actions > Release API > Run workflow, dispatched frommain, withenvironment=productionandbranch=main. Confirm the selected commit is deployed and a pt-BR validation response contains orderederrorsstrings and matchingerrorDetailsentries before releasing the consumer.repo:uiticket from the handoff and links it to Aggregate reminder scheduler probe #606 and this PR. That ticket must consumeerrorDetails, retain the legacy response fallback, and send the selected request language for signed-out validation. Its release follows the API deploy.