Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[Translation] Translatable objects#37670

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
fabpot merged 1 commit intosymfony:masterfromnatewiebe13:translatable-objects
Aug 28, 2020

Conversation

@natewiebe13
Copy link

@natewiebe13natewiebe13 commentedJul 26, 2020
edited
Loading

QA
Branch?master
Bug fix?no
New feature?yes
Deprecations?no
TicketsNone
LicenseMIT
Doc PRsymfony/symfony-docs#... - Will be added if this gets approval

Scenario
When writing business logic, this code typically lives within services. With complex situations, there can be lots of business logic exceptions or messages being created. In order to have the best user experience, we try to display detailed messages to the end user so if it's an error with some data, they know what they need to do to correct the situation. Sometimes this is through a message on a page, an email, etc. In some cases a generic message might be displayed and we log the message instead or send the message in an email to a 3rd party.

The service shouldn't need to care how the message is going to be handled, but just needs to create it in a way that it can be translated if needed.

Options
Translating when the message is created. There are two issues with this approach. Autowiring the translator interface to services that will display an error isn't ideal as it will increase the dependencies the service has. The larger issue is that because we would be throwing an exception that contains a string or passing a message that's a string (what the translator returns), if we have generic twig templates that run the translator function on a message, we're doing a double translation and the debug toolbar will indicate that there is a missing message and turn red. This makes finding missing translations much more difficult as we have to determine if there are any false positives and turns the red indicator into white noise.

Translating when displaying on a template. This option is possible, but adds complexity to our templates. Each time we display a message, we need to have some kind of logic that checks to see if it should be ran through the translator as-is or we need to get the data from the object and pass into the translator.

Proposed Solution
Adding a Phrase object that we can create and pass around reduces the template complexity and adds a nice way to create objects that can be translated at any point. This something that will be very familiar with Magento developers and other platforms also have similar solutions.

TODO

  • Determine if overwriting the parameters should be allowed (currently implemented this way)
  • Determine the name of the translatable class, currentlyPhrase asMessage will likely get confused with Messenger
  • Determine if there should be a global function to allow quickly creating these. Similar to__() in Magento
  • Add docs
  • Add tests

Happy to get as much feedback as possible. I know a feature like this would be very welcomed by my team.

linaori, ro0NL, wtorsi, jvasseur, and rmikalkenas reacted with thumbs up emojidmaicher reacted with confused emojipizzaminded reacted with eyes emoji
@linaori
Copy link
Contributor

I like this implementation, primarily because you can add parameters to the message without having to define that elsewhere. Meaning you can return this object rather than having to return and array where the first key is the message, or a custom data container etc.

In the past I've built something similar where a process would create a similar object and then it translate this in another request:

{{translatableMessage.key | trans(translatableMessage.parameters) }}

I like this idea because you push the dependency of translating to where it belongs; the view. I suspect this would also benefit translations within the form component where you can easily pass parameters if needed.

@ro0NL
Copy link
Contributor

im wondering what type of objects would be "translatable", esp. in direct relation to a single message.

To me it hints for a value object instead, rather then a contract.

push the dependency of translating to where it belongs; the view

does this mean only thetrans view layer should be translatable aware?

@linaori
Copy link
Contributor

To me it hints for a value object instead, rather then a contract.

You'll need the contract to get the message key and parameters, possibly domain and language.

does this mean only thetrans view layer should be translatable aware?

This is automatically the case if the translator can deal with it.

@ro0NL
Copy link
Contributor

i meant interface.final class Translatable offers the same feature.

@linaori
Copy link
Contributor

Can do that, though I can also see advantages of (serializable) custom objects.

@xabbuhxabbuh added this to thenext milestoneJul 27, 2020
@natewiebe13
Copy link
Author

i meant interface.final class Translatable offers the same feature.

I like the idea of puttingfinal on the implementation class in the PR. I do think we should still have an interface so we don't have to force a specific implementation, especially with making it final, but maybe I've misunderstood your comment.

One other example I'd like to provide, is at the end, it would be great to be able to use$this->addFlash('Successfully imported products.'); or$this->addFlash(__('Successfully imported %count% product(s)', ['%count%' => count($products])); and inside of the template just being able to translate all flash messages using{{ message|trans }}. It would remove complexity and having to really think too much about how the message is going to be used.

@ro0NL
Copy link
Contributor

ro0NL commentedJul 27, 2020
edited
Loading

I do think we should still have an interface so we don't have to force a specific implementation

do you have any other implementation in mind? I dont see polymorphism being a requirement here.

Im not sure i follow your "addFlash" example, but it could account for Translatables as a feature.

Not sure about the__() example either, but see#35418 also. I think a generict('id') funtion for extraction is nice yes, but alsonew Translatable('id') could be marked for extraction.

@natewiebe13
Copy link
Author

do you have any other implementation in mind? I dont see polymorphism being a requirement here.

I don't have any in mind at the moment, but potentially someone else might have a reason for them. We could just add a concrete class only, but though it might be a good to allow flexibility there. Users could also just use their own translator service to get around this limitation as well. Let me know if you want me to remove the interface.

Im not sure i follow your "addFlash" example, but it could account for Translatables as a feature.

In a basic CRUD setup, typically we'd add a flash message saying what was done and display it as a flash message on the next page. Using a translatable object would be great to avoid having to pass an already translated string and being able to just display on the next page without special handling or having Symfony think we're missing a translation, as we would like to just always translate the message in the view.

Not sure about the __() example either, but see#35418 also. I think a generic t('id') funtion for extraction is nice yes, but also new Translatable('id') could be marked for extraction.

__() is used in Magento [docs] and_e() in Wordpress [docs]. I believe these would be similar tot() like you mention in#35418. I think the leading underscore is a convention when creating translatable strings, so potentially_t() instead of just the t? Basically it would be a shortcut fornew Translatable() as you mention.

I'm open to either way of dealing with this. Let me know what direction you'd like this to take.

@apfelbox
Copy link
Contributor

apfelbox commentedJul 28, 2020
edited
Loading

i meant interface.final class Translatable offers the same feature.

We have built basically the same feature here:https://github.com/Becklyn/rad/blob/8.x/src/Translation/DeferredTranslation.php

We have some additional functionality, where not all of it makes sense here:

We have the concept that we either pass a string (not to-be-translated) or aDeferredTranslation.

  • We have named constructors:DeferredTranslation::messages($id),DeferredTranslation::backend($id), ...
  • We have a static::translateValue($value) method, that either translates theTranslatable or return the already pre-translated string.
  • We have a::ensureValidValue() static method, that checks if the value is a either a string,Translatable or (optionally)null and automatically throws anUnexpectedTypeException.

The point is: from our POV the whole "translate this without using the translator" is a core feature and without these additional use cases it is really cumbersome to use.
But not all of these additional points make sense for Symfony core. So at least don't make the classfinal, so that one can add these features in their own lib / project.

PS: in terms of ourDeferredTranslation, we have the same feature forDeferredRoute (create a URL without needing a dependency on the router,very frequently used) as well asDeferredForm (preparing and later manipulating the form config for->createForm(...) without using any form service, only used in a single special case).

@linaori
Copy link
Contributor

But not all of these additional points make sense for Symfony core. So at least don't make the class final, so that one can add these features in their own lib / project.

I don't see the added benefit of this. There's avery specific dataset you put in the data object. Handling these custom translations arenot a part of the data object, nor should they be. If you need to do something custom, you can use composition (if the interface stays).

@apfelbox
Copy link
Contributor

If the interface stays, that would be fine as well.

Yeah, but for example things like merging parameters is (imo) a core feature. So if it's decided that this doesn't make it to theTranslatable VO, then at least we need the interface. Otherwise I fear that is a super specific use case, that's not flexible enough for a lot of use cases.

There were quite a few of these in the past, so I'm just trying to chime in to avoid that here. Sorry for the noise.

@ro0NL
Copy link
Contributor

Merging parameters y/n would be a Translator implementation detail :)

@natewiebe13
Copy link
Author

Hey@ro0NL, can you take another look at this? Once you're happy with where things are at, I think it would make sense to add a shorthand function (currently leaning towards_t()) and add some tests.

/**
* @return string The message id (may also be an object that can be cast to string)
*/
publicfunctiongetMessageId():string
Copy link
Member

Choose a reason for hiding this comment

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

I would usegetMessage() as a message can be a plain English sentence or an id.

welcoMattic reacted with thumbs up emoji
Copy link
Contributor

Choose a reason for hiding this comment

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

@fabpot i proposed messageId. I think it's semantically more clear, as ultimately thisis the ID value (even if it's a plain English sentence).

Copy link
Member

Choose a reason for hiding this comment

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

message is what is used everywhere in the component, we don't use message id anywhere, so I still think that message should be used here.

Copy link
Author

Choose a reason for hiding this comment

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

I agree with this. Updated the PR.

@natewiebe13natewiebe13 changed the title[Translator] Translatable objects[Translation] Translatable objectsAug 15, 2020
@fabpot
Copy link
Member

Can you rebase on current master (to remove the merge commit)?

@natewiebe13
Copy link
Author

@fabpot rebased onto master

@fabpot
Copy link
Member

Thank you@natewiebe13.

@natewiebe13natewiebe13 deleted the translatable-objects branchSeptember 2, 2020 13:35
javiereguiluz added a commit to symfony/symfony-docs that referenced this pull requestSep 25, 2020
This PR was squashed before being merged into the master branch.Discussion----------Translatable objectsGoal: Add docs for translatable objects.Feature:symfony/symfony#37670Fixes#14145Unsure how in-depth the docs should go. Open to feedback and collaboration.Commits-------a2ad274 Translatable objects
@nicolas-grekasnicolas-grekas modified the milestones:next,5.2Oct 5, 2020
@fabpotfabpot mentioned this pull requestOct 5, 2020
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@fabpotfabpotfabpot approved these changes

@jderussejderussejderusse left review comments

+4 more reviewers

@gharlangharlangharlan left review comments

@linaorilinaorilinaori left review comments

@ro0NLro0NLro0NL approved these changes

@maxheliasmaxheliasmaxhelias approved these changes

Reviewers whose approvals may not affect merge requirements

Assignees

No one assigned

Projects

None yet

Milestone

5.2

Development

Successfully merging this pull request may close these issues.

11 participants

@natewiebe13@linaori@ro0NL@apfelbox@fabpot@gharlan@jderusse@maxhelias@nicolas-grekas@xabbuh@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp