-
Notifications
You must be signed in to change notification settings - Fork 10k
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
Enhancements to @bind - globalization and conversions #9386
Comments
If possible I would like to add my 2 cents to the discussion about culture. In my opinion the best practice is (example for 2 cultures):
How to decide which culture to use?
Why is this the best practice?User experienceLet's test it on the most complicated example. We have a web page with support for en-us and pl-pl culture. Default culture is pl-pl because I live in Poland and our customers are mainly from Poland. User from Germany visits our main page. We don't support de-de culture - he is redirected to example.com/pl-pl. Unfortunately this user doesn't speak Polish but he can easily switch to English. Next day he visits our page again. He has entered example.com and he is automatically redirected to example.com/en-us - his preferred culture on our site. SEOEvery page have to have distinct address. You cannot display different content (language) for the same url based on some "magic" like cookie. As far as I know search engines index only one content per url. Some guidelines from Google: https://support.google.com/webmasters/answer/189077?hl=en Works everywhere (server, client, prerendering, api)We have only one page without culture in the url - the main page, which essentially does not render anything because it is only used to redirect user to the page with culture. Every other page has culture in the url so it is easy to find this information on the server (including prerendering) and on the client (Blazor working in web assembly). Blazor can also call web api with culture in the url (example.com/en-us/api/products) With this information in the URL server can return product names and descriptions in proper language. Of course culture can be encoded in query string instead of url, but in my humble opinion url is better. Proof that it worksYou can check this site: tajskiespa.pl to see how it works in practice (it was written in ASP.NET Core 1.1 and it supports pl and en language, not full culture.). You can also check how it is displayed in search results. In Google search for "tajskie spa wilanow branickiego". If you have English preference in the browser you should see link to https://tajskiespa.pl/en/our-salons/wilanow-branickiego (scroll down if you don't use add blocker and you see some Google adds at the top). Change your browser to prefer Polish language and you should see link to https://tajskiespa.pl/pl/nasze-salony/wilanow-branickiego. I understand that good framework is probably not opinionated, but at the same time it should promote best practices and as you can see culture in the url (eventually query string) is the best solution which at the same time probably resolves all problems.
Implement solution which is in line with best practices and don't worry about people who want something fancy. What is not acceptableYou should not use Accept-Language header for anything other than redirection from main page. Example: English speaking tourist visits Paris. In the hotel or internet cafe he uses French browser to look for some attractions. He has no idea what Accept-Language header is and how to change it in the browser. The only chance for him is to change the culture using the user interface available on the website. |
thanks for the comments @Andrzej-W - I'm going to look at putting this together as a sample and see what the blockers are. |
Super rudimentary sample of the culture propagation part of this here: https://github.com/rynowak/ServerSideBlazorLocalization This uses the localization middleware (as well as some other custom middleware) to set a cookie on the client. I also created a custom thing to treat the locale as part of the path+pathbase. I don't generally feel positively about using routing to track the culture - it's massively complex, and it feels wrong when you have the assumption that you're going to use a consistent localisation scheme for your app. Due to the fact that I'm using a cookie for locale, it works for free in server-side Blazor, because I can just rely on the same localisation middleware. This also makes things easy in client-side Blazor because the cookie is available to the app trivially. What I don't like about this is that we need to find a home for all of the custom middleware that this requires. We'd never put something like this in a sample directly, it's too nuanced. |
@rynowak Unfortunately we cannot ignore the fact that any web site have to be properly indexed by search engines. Otherwise this site simply "does not exists". Recently I have read the study and it looks that people who want to open As I have already written in my previous post each multilingual site should have different urls for the same page in different languages (cultures). It is necessary for proper indexing. Now when you have different urls for each culture search engine will send you directly to appropriate address. If you visit this site the first time (no cookie in the browser) there are two ways to select the culture: url or Accept-Language header. You have seen in my previous post that Accept-Language header is far from perfect for people who need to use not their own computers. There are a few possible url schemes (I will use
I have never seen option 1, at least I don't remember. The most popular are options 2 and 3. We can say that urls in option 2 are fully localized and it is probably the best from the SEO point of view. Option 3 is probably the easiest to implement for programmers. We have to remember that each page have to have links to itself and all other languages/cultures (SEO requirements). <link rel="alternate" hreflang="pl-pl" href="http://example.com/pl-pl/kontrahenci" />
<link rel="alternate" hreflang="en-us" href="http://example.com/en-us/customers" /> We also want to have "language switcher" UI element on every page. When we switch the language we should stay on the same page. Option 3 is the easiest because we (programmers) only have to change culture in the url to create alternate addresses. Personally I prefer option 2. Options 4 and 5 are probably seldom used. Usually different subdomain means that web server is physically located in different data center (on different continent) to optimize access time for users around the globe. But in that case application is not really multilingual - each application instance serves pages for only one culture. It can be complex but I think we have no other choice - urls should be tightly coupled with culture. If someone is not interested in true multilingual/multiculture application then it should be possible to set some default culture and use it on the server and on the client. Eventually single language web site can support multiple cultures and this for simplicity can be based on Accept-Language header (first request) and then you can use cookies. Here you don't need different urls because you always display the same content and culture is probably only used for date and numbers formatting. |
@Andrzej-W - thanks for your input on this. When I said that I didn't think routing was a good solution for this, I didn't mean that URLs aren't a good solution. Ultimately I think you're right and I'm wrong after reading your reply. I'm torn on this because we don't currently offer a solution for translated URLs. I did a little bit of sample building based on what you described. The part that's not going to easily be possible in this release is translating the URL path into various languages. The routing system in Blazor is pretty primitive right now. As far as what we're shipping in the box, we're going to recommend that users use a cookie as the source of truth for server-side blazor. This works with the SignalR services, with self-hosted SignalR, and is already supported by ASP.NET Core. To set this cookie, or decide what locale to give users by default, that's going to be up to you to figure out. We're not going to build in a specific localization scheme to server-side Blazor. |
Edit: @rynowak hijacking the top post for great justice
Summary
@bind
when used with<input ...>
elements is the primary way that users parse and convert user input into .NET types. This makes@bind
the primary touch point for globalization - dealing with input and display of information in a locale specific way.Goals:
TLDR My recommendations for 3.0:
Client-sidewill be done post 3.0type="..."
mappings to support culture and formats@bind
support for:DateTime?
DateTimeOffset
DateTimeOffset?
TypeConverter
CUT
byte
TypeConverter
)Related user-feedback
Challenges
HTML5 field definitions make dealing with globalization more complicated, because depending on the field type, the culture-sensitivity expectations are different.
In the
type="text"
field the.value
attribute will contain exactly what the user typed in - this implies that the value needs to parsed, or formatted according the users' locale.In the
type="number"
field the.value
attribute will contain a number value in an standard non-locale-specific format. The value must be parsed and formatted according to this standard format. It's up to the browser to support the users' preferred style of display for their locale.It stands to reason that users will want to use
text
fields for numbers, dates, etc in some cases instead of the HTML5 equivalents. Usingtext
is much more flexible since it's essentially free-form text, as opposed tonumber
which is restricted by the browser, and has a set of UI behaviors the developer might not want.Additionally, for the server-side Blazor programming model, there's no automatic way to flow the culture from the client to the server. We have to build it in to Blazor.
Plan of record
Since some fields require the use of
CultureInfo.InvariantCulture
and some require hardcoded formats (type="month"
) we need to make our parsing and formatting code aware of these details. This knowledge needs to either come from the runtime (JS or .NET), the compiler, or the user.At first glance it seems like it would be straightforward to put this knowledge in the JS part of the runtime. At this layer we can see each DOM element and it's attributes, so we should be able to tell what type of field we're interacting how. However, this has a few drawbacks. First, we'd need to pull in some JS glob/loc packages, which would increase the size. Secondly, we'd be hoping and assuming that these JS libraries have the same behaviors as .NET. That doesn't seem reasonable.
So we settled on a hybrid approach. We'll allow the user to specify the culture or format, but try to get it right for the known
type="..."
values. This means that if we seetype="number"
statically in code, we can apply the right culture. If the user wants to assign the field type dynamically then they will need to specify the format or culture manually.Ideas and discussion
More control over
@bind
Some ideas here... we don't have to do all of these, and probably shouldn't try to do all of these in the first release.
Flow the field type into .NET: We could easily capture data about the field type in the JS side of the binding code. This would allow the .NET code to be aware of what the
type="..."
attribute specifies - which we could use to perform the conversion using invariant culture or the user's culture. With this done, we could make the default be culture-sensitive, and apply invariant culture where appropriate based on the field type.Allow users to specify the culture for
@bind
We could allow users to specify the culture at the call-site. This could be a desirable level of control for application developers, but it's not a good default experience - essentially we'd be picking a default behavior and allowing an override. This isn't a good default user experience by itself - because as we've already seen, HTML5 fields likenumber
want a different default than `text.Directive attributes make it easy for us to build something like this
Allow format strings for more types Currently we support format strings for
DateTime
and only datetime. This is useful becauseDateTime
is used to represent a variety of different kinds of values, times, dates, both, etc. This is also useful becauseDateTime
supports a format string on both formatting and parsing.It might seem like allowing format strings on more types will free us from the need to do some of these other solutions - however:
DateTime
don't use format strings for parsing. (ouch)So while format strings might be useful, they don't really solve any globalization related problems.
Its definitely possible to support format strings for more types, but we need to make sure users understand that the format string they specify is only used in formatting, and not in parsing. The parsing behaviour is specified by
NumberStyles
. This implies that we need to be really permissive in what we parse if we want users to specify a format for numbers.Add support for missing types We could add support to
bind
for types that don't currently support likeDateTimeOffset
,DateTime?
and a bunch of the rarely-used integral types. Look at the types supported bySystem.Convert
Since
bind
currently only supports enums and the types we hardcode we don't have support for types likeushort
orbyte
. It seems obvious to me that there's a priority order of these things:DateTimeOffset
&DateTimeOffset?
&DateTime?
I think an important question when you consider more exotic types like
char
orsbyte
is - what is the expectation of someone who reaches for that type?. I don't think we know. It's easy to try and understand the motivation of someone usingDateTime
orint
ordecimal
.It gives me a little bit of pause when I think about this because I don't know what that user wants - do they want Hex? Octal? Why?
Paired with adding support for type-converters, we could end up in a scenario where not providing hand-coded
bind
support in this release and adding it in a future release results in a breaking change.Add support for type converters We could easily add support for arbitrary types to fall back to type converters. Type convert is a well-known extensibility point that .NET provides, and other parts of ASP.NET Core use for conversions.
This seems like a clear win, because we get support for things like
Guid
andTimeSpan
without writing much additional code. Users can write their ownTypeConverter
and plug it in using a non-Blazor mechanism and it will just work.This is actually somewhat high priority because users hit this trying to use
@bind
with a generic type. Since we don't overload@bind
for arbitrary types, this will result in a compile error and a bad experience.Allow users to specify a custom converter` We could allow users to specify a converter at the call-site. This could be a desirable level of control for application developers when they are doing something complex.
Directive attributes make it easy for us to build something like this
However, this doesn't really solve any globalization problems. This is just added flexibility.
Flow the culture
We have to make sure that .NET code is running with the correct
CultureInfo.CurrentCulture
andCultureInfo.CurrentUICulture
. This is required for localization (using.resx
as well as doing any kind of culture-specific conversion).Having the properties reflect the user's locale is essential for making Blazor feel like .NET because without this, a bunch of .NET infrastructure will work incorrectly. You normally accomplish this in .NET using the localization middleware and
Accept-Language
, but I'm not sure that applies to our scenario. Asterix 1Concerns that need to be handled:
This of course is considering that we can assume
navigator.language
reflects what the user wants to see. I think we need to consider providing an override or some other mechanism to customize this to be out of scope for 3.0 and react to feedback.Appendix
Asterix 1
Users really like to get fancy with the localization middleware and do all kinds of things that aren't based on
Accept-Language
. Typically means using the query string or URL path. These mechanisms aren't inherently visible to JS in the same way thatAccept-Language
would be - since the browser generatesAccept-Language
.I expect we're going to a substantial amount of feedback from users that want something fancy to work, but I don't see a way we can make that possible for either Blazor scenario. The trap here is that the localization middleware will be required for pre-rendering, but won't be used for non-prerendering - resulting in subtle bugs in users' applications. The solution would be for users to write their own logic to resolve the culture (in JS).
The text was updated successfully, but these errors were encountered: