Closed Naoto-Ida closed 8 years ago
投稿してくれてありがとうございます!
This is a great question. I believe that the way we handle this is implicitly. As in, both the authenticated user and their language preference are part of the GraphQL executor context, as opposed to being passed explicitly as arguments to any field.
Cc @dschafer - any suggestions here?
As in, both the authenticated user and their language preference are part of the GraphQL executor context, as opposed to being passed explicitly as arguments to any field.
Yep, that's exactly what we do, and we haven't run into too many issues.
First of all let fix args names in your resolver(obj, args, context, info)
:
resolve: (parent, args, context, { rootValue }) => {
const { language } = rootValue
const { BookLoader } = rootValue.loaders
return BookLoader.TranslationsLoader(language).load(parent.id).then(translations => translations[parent.id].title || parent.title)
}
DO NOT use rootValue
for session data (request). rootValue
is same for all requests, but context
not. So use context
for storing language
value per request.
To solve your problem write Viewer.resolve
in such manner:
resolve: (obj, args, context, info) => {
context.language = args.lang || 'en';
return {}; // or may return somedata due your bussiness logic
}
Viewer resolver should write language
to context
.
So after that all underlying resolvers should get lang via context:
resolve: (parent, args, context, { rootValue }) => {
const { language } = context;
const { BookLoader } = rootValue.loaders
return BookLoader.TranslationsLoader(language).load(parent.id).then(translations => translations[parent.id].title || parent.title)
}
Thank you guys for the input (and the Japanese too!).
I think the first thing I need to confirm is my understanding of viewer
.
In Facebook's case, viewer
is a representation of the user, correct?
And how would you go about passing "who" the user is? And if theres a generic list of articles, but targeted articles based on who the viewer
is, how would one go about doing that?
// General list of articles
{
articles {
id
title
}
}
{
viewer { // knows its me
articles { // articles based on my 'liked' artist on our services
id
title
}
}
}
Sorry for the mountain of questions. My team is very excited about the possibilities, but am worried about our implementation of things being uncommon, so we want to get it right. Would love to get 1:1 with one of your for about 30 min. if any of you are gracious enough.
What do you do in cases where they aren't logged in? Our user base is mainly Japanese, but have a lot of English speakers as well, so I want to avoid defaulting to Japanese as much as possible, i.e. modifying nodkz's example:
resolve: (obj, args, context, info) => {
context.language = args.lang || 'ja';
return {}; // or may return somedata due your bussiness logic
}
Btw, I am using it for our music news app, which gets its data from our brand new GraphQL API.
In Facebook's case, viewer is a representation of the user, correct?
For FB, I think that's correct. But I've seen quite a number of Relay implementations that use this field for dual purposes: 'a currently authenticated user' and 'a gateway to GraphQL information' (ala File Explorer). So viewer.stockPrices()
can exist as a field, even if it has nothing to do with the 'current user'. For us, we don't use this naming convention at all, and replace it with a business-level name (in music, an equivalent could be a field called catalogue
).
And how would you go about passing "who" the user is?
Our frontend injects the Authorization
header with every Relay query (using react-relay-network-layer). For all requests, the auth token is extracted from this header, converted to the domain-level ACL object/rules, and attached to the root-level context
for GraphQL to consume. Therefore, the 'current user' information is available across all fields, including viewer
, node
, and any others. Then each field is free to respond with generic public information, or user-specific information for those who are logged in.
Similar to auth, another strategy for you could be to use the express route handler to check the request header for Accept-Language
(or a custom parameter passed by the frontend), and then set context.language
to the appropriate value for GraphQL to use (and it will be available in the node
field as well). Then the lang
argument can be an optional override for any specific field (if required).
@eugene1g Thanks for sharing your implementation ideas!
It's very interesting to hear how other people have implemented it.
I've never thought of using Accept-Language
... will give it a go.
@Naoto-Ida. Just like @eugene1g described above in our App we parse select Headers
and make them available the GraphQL context.
@rturk Thank you! We went with the discussed implementations and our service is running better than ever. Hopefully soon we'll be able to apply for getting on the list of apps using Relay. Thank you guys.
@Naoto-Ida no need to apply, just send us a PR to add your company's name to the USERS.md file.
@josephsavona Great! Thank you so much.
(*This has been asked on SO over a week ago, but thought it be better to go to the source). What is the recommended way of localizing results, and how would I pass a locale?
We have a multilingual system, which means that the results are localized. So we have an argument called lang which is passed down to the field resolvers. Queries are written as such:
Which would return:
This is possible due to the resolver for translatable fields looking like this (note that I am using dataloader):
If a translation exists, it will use that instead of the original value. This was working fine until we introduced Relay into our project.
We want to utilize Relay's node, and have made it partially work, i.e.:
But, since using node can't take arguments like viewer can, there is no way to pass the language.
How do you guys at Facebook deal with language in general, regardless of using Relay with GraphQL?