Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Support for Translatable Validation Attribute Errors #4070

Open
wants to merge 14 commits into
base: 1.x
Choose a base branch
from

Conversation

DavideIadeluca
Copy link
Contributor

@DavideIadeluca DavideIadeluca commented Oct 12, 2024

Fixes #0000

Note

This PR aims to implement the translation of attributes of validation errors, a feature initially started nearly a decade ago (see this commit) but seemingly never completed.

Changes proposed in this pull request:

Reviewers should focus on:

  • First and foremost, evaluate if this change is not a breaking change and can safely be done on 1.x. The only concern with my implementation is that some extensions may have already defined a key in the validation namespace, like validation.attributes.name, which could lead to unexpected behavior. In any case, I recommend reading the discussion in Move locale files from language pack to core #2408 (comment) to get more context on this.

  • Would there be a way how the same could be achieved without having to explicitly return a translation key for each attribute? Let's say there was some kind of generic solution, how would we handle cases such as this:

    protected $rules = [
    'minimum-stability' => ['sometimes', 'in:stable,RC,beta,alpha,dev'],
    'repositories' => ['sometimes', 'array'],
    'repositories.*' => ['sometimes', 'array', 'required_array_keys:type,url'],
    'repositories.*.type' => ['in:composer,vcs,path'],
    'repositories.*.url' => ['string', 'filled'],
    ];

  • The only validation logic I knowingly didn't change is the SettingsValidator, as in that case it would certainly not be maintainable to add support for every possible case:

    protected $globalRules = [
    'max:65000',
    ];
    /**
    * Make a new validator instance for this model.
    *
    * @param array $attributes
    * @return \Illuminate\Validation\Validator
    */
    protected function makeValidator(array $attributes)
    {
    // Apply global rules first.
    $rules = array_map(function () {
    return $this->globalRules;
    }, $attributes);
    // Apply attribute specific rules.
    foreach ($rules as $key => $value) {
    $rules[$key] = array_merge($rules[$key], $this->rules[$key] ?? []);
    }
    $validator = $this->validator->make($attributes, $rules, $this->getMessages());
    foreach ($this->configuration as $callable) {
    $callable($this, $validator);
    }
    return $validator;
    }

  • Should a similar solution be PR'd for 2.x or are you looking for something more lean perhaps without the risk of a breaking change?

Screenshot

QA

  1. Install a language pack such as flarum-lang/german or flarum-lang/dutch
  2. Switch the Forum UI to another language (not english)
  3. Try to submit a new discussions with no title or content. You should now see the translated version of title or content

Necessity

  • Has the problem that is being solved here been clearly explained?
  • If applicable, have various options for solving this problem been considered?
  • For core PRs, does this need to be in core, or could it be in an extension?
  • Are we willing to maintain this for years / potentially forever?

Confirmed

  • Frontend changes: tested on a local Flarum installation.
  • Backend changes: tests are green (run composer test).
  • Core developer confirmed locally this works as intended.
  • Tests have been added, or are not appropriate here.

Required changes:

  • Related documentation PR: (Remove if irrelevant)
  • Related core extension PRs: (Remove if irrelevant)

@DavideIadeluca DavideIadeluca marked this pull request as ready for review October 12, 2024 18:49
@DavideIadeluca DavideIadeluca requested a review from a team as a code owner October 12, 2024 18:49
@SychO9
Copy link
Member

SychO9 commented Oct 12, 2024

It should be possible to edit AbstractValidator and manipulate the messages to dynamically check for the existence of a validation attribute translation. Mainly, the result of getMessages.

@SychO9
Copy link
Member

SychO9 commented Oct 12, 2024

The only concern is potential conflicts in attribute names between extensions. So might be better to put extension validation under the extension namespace.

Copy link
Member

@imorland imorland left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @SychO9 suggests, we run the possibility of conflicts here if extensions register the same attribute names, but want a different translation.

Let's scope everything under the extension name(s) please

@DavideIadeluca
Copy link
Contributor Author

@imorland I moved the validation translations to the extension namespaces

It should be possible to edit AbstractValidator and manipulate the messages to dynamically check for the existence of a validation attribute translation. Mainly, the result of getMessages.

@SychO9 Can you make an example of what you were thinking? Now that some validation strings are in the extension namespaces, I feel like it would get messy if the AbstractValidator has to support both the structure of core and extensions but maybe I'm missing something here

@SychO9
Copy link
Member

SychO9 commented Oct 19, 2024

here is a proof of concept of how you can obtain the extension ID related to the validator:

// this would be nice in a trait, used by the abstract validator in this case
protected function getClassExtensionId(): ?string
{
    $extensions = resolve(ExtensionManager::class);

    return $extensions->getExtensions()
                ->mapWithKeys(function (\Flarum\Extension\Extension $extension) {
                    return [$extension->getId() => $extension->getNamespace()];
                })
                ->where(function ($namespace) use ($class) {
                    return $namespace && str_starts_with(static::class, $namespace);
                })
                ->keys()
                ->first();
}

you would have to add this to Extension.php

    public function getNamespace(): ?string
    {
        return Collection::make($this->composerJsonAttribute('autoload.psr-4'))
            ->filter(function ($source) {
                return $source === 'src/';
            })
            ->keys()
            ->first();
    }

once you have that, you could theoretically use it in the getMessages method to automatically point to $extId.validation.attributes.$attribute or validation.attributes.$attribute if core (extId null)

also imo it's not necessary to replace . with _ for attribute locale key.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants